Browse Source

Merge pull request #10 from cnAbp/Translate

Translate
pull/4228/head
maliming 8 years ago
committed by GitHub
parent
commit
f8faea7ad5
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 158
      docs/zh-Hans/AspNetCore/Bundling-Minification.md
  2. 86
      docs/zh-Hans/AspNetCore/Client-Side-Package-Management.md
  3. 176
      docs/zh-Hans/Best-Practices/Application-Services.md
  4. 2
      docs/zh-Hans/Best-Practices/Data-Transfer-Objects.md
  5. 2
      docs/zh-Hans/Best-Practices/Domain-Services.md
  6. 112
      docs/zh-Hans/Best-Practices/Entities.md
  7. 2
      docs/zh-Hans/Best-Practices/Entity-Framework-Core-Integration.md
  8. 164
      docs/zh-Hans/Best-Practices/Module-Architecture.md
  9. 69
      docs/zh-Hans/Best-Practices/MongoDB-Integration.md
  10. 52
      docs/zh-Hans/Best-Practices/Repositories.md
  11. 2
      docs/zh-Hans/Entity-Framework-Core.md
  12. 166
      docs/zh-Hans/Exception-Handling.md
  13. 2
      docs/zh-Hans/Index.md
  14. 131
      docs/zh-Hans/Multi-Tenancy.md
  15. 207
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-I.md
  16. 114
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-II.md
  17. 52
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-III.md

158
docs/zh-Hans/AspNetCore/Bundling-Minification.md

@ -1,24 +1,25 @@
## ASP.NET Core MVC Bundling & Minification ## ASP.NET Core MVC 捆绑 & 压缩
There are many ways of bundling & minification of client side resources (JavaScript and CSS files). Most common ways are: 有许多方法可以捆绑&压缩客户端资源(JavaScript和CSS文件). 最常见的方式是:
* Using the [Bundler & Minifier](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.BundlerMinifier) Visual Studio extension or the [NuGet package](https://www.nuget.org/packages/BuildBundlerMinifier/). * 使用Visual Studio[捆绑&压缩](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.BundlerMinifier)扩展或者其它的[NuGet相关包](https://www.nuget.org/packages/BuildBundlerMinifier/).
* Using [Gulp](https://gulpjs.com/)/[Grunt](https://gruntjs.com/) task managers and their plugins.
ABP offers a simple, dynamic, powerful, modular and built-in way. * 使用[Gulp](https://gulpjs.com/)/[Grunt](https://gruntjs.com/)及其插件.
### Volo.Abp.AspNetCore.Mvc.UI.Bundling Package ABP内置了简单,动态,强大,模块化的方式.
> This package is already installed by default with the startup templates. So, most of the time, you don't need to install it manually. ### Volo.Abp.AspNetCore.Mvc.UI.Bundling 包
Install the `Volo.Abp.AspNetCore.Mvc.UI.Bundling` nuget package to your project: > 默认情况下已在启动模板安装此软件包. 大多数情况下,你不需要手动安装它.
将`Volo.Abp.AspNetCore.Mvc.UI.Bundling` nuget包安装到您的项目中:
```` ````
install-package Volo.Abp.AspNetCore.Mvc.UI.Bundling install-package Volo.Abp.AspNetCore.Mvc.UI.Bundling
```` ````
Then you can add the `AbpAspNetCoreMvcUiBundlingModule` dependency to your module: 然后将`AbpAspNetCoreMvcUiBundlingModule`依赖项添加到你的模块上:
````C# ````C#
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
@ -36,7 +37,7 @@ namespace MyCompany.MyProject
### Razor Bundling Tag Helpers ### Razor Bundling Tag Helpers
The simplest way of creating a bundle is to use `abp-script-bundle` or `abp-style-bundle` tag helpers. Example: 创建bundle的最简单方法是使用`abp-script-bundle`或`abp-style-bundle` tag helpers. 例如:
````html ````html
<abp-style-bundle name="MyGlobalBundle"> <abp-style-bundle name="MyGlobalBundle">
@ -47,26 +48,26 @@ The simplest way of creating a bundle is to use `abp-script-bundle` or `abp-styl
</abp-style-bundle> </abp-style-bundle>
```` ````
This bundle defines a style bundle with a **unique name**: `MyGlobalBundle`. It's very easy to understand how to use it. Let's see how it *works*: `abp-script-bundle`定义了一个带有**唯一名称**的样式包:`MyGlobalBundle`. 使用方法很容易理解. 让我们看看它是如何*工作的*:
* ABP creates the bundle as **lazy** from the provided files when it's **first requested**. For the subsequent calls, it's returned from the **cache**. That means if you conditionally add the files to the bundle, it's executed only once and any changes of the condition will not effect the bundle for the next requests. * 当首次请求时,ABP从提供的文件中 **(延迟)lazy** 创建. 后续将从 **缓存** 中返回内容. 这意味着如果你有条件地将文件添加到包中,它只执行一次, 并且条件的任何更改都不会影响下一个请求的包.
* ABP adds bundle files **individually** to the page for the `development` environment. It automatically bundles & minifies for other environments (`staging`, `production`...). * 在`development`环境中ABP会将包文件**单独**添加到页面中, 其他环境(`staging`,`production`...)会自动捆绑和压缩.
* The bundle files may be **physical** files or [**virtual/embedded** files](../Virtual-File-System.md). * 捆绑文件可以是**物理**文件或[**虚拟/嵌入**](../Virtual-File-System.md)的文件.
* ABP automatically adds **version query string** to the bundle file URL to prevent browsers from caching when the bundle is being updated. (like ?_v=67872834243042 - generated from last change date of the related files). The versioning works even if the bundle files are individually added to the page (on the development environment). * ABP自动将 **版本查询字符串(version query string)** 添加到捆绑文件的URL中,以防止浏览器缓存. 如:?_v=67872834243042(从文件的上次更改日期生成). 即使捆绑文件单独添加到页面(在`development`环境中), 版本控制仍然有效.
#### Importing The Bundling Tag Helpers #### 导入 Bundling Tag Helpers
> This is already imported by default with the startup templates. So, most of the time, you don't need to add it manually. > 默认情况下已在启动模板导入. 大多数情况下,你不需要手动安装它.
In order to use bundle tag helpers, you need to add it into your `_ViewImports.cshtml` file or into your page: 要使用`bundle tag helpers`, 你需要将其添加到`_ViewImports.cshtml`文件或页面中:
```` ````
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling @addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling
```` ````
#### Unnamed Bundles #### 未命名的 Bundles
The `name` is **optional** for the razor bundle tag helpers. If you don't define a name, it's automatically **calculated** based on the used bundle file names (they are **concatenated** and **hashed**). Example: 对于razor bundle tag helpers, `name`是**可选**. 如果没有定义一个名字,它将根据使用的捆绑文件名自动**计算生成**(they are **concatenated** and **hashed**) 例:
````html ````html
<abp-style-bundle> <abp-style-bundle>
@ -80,33 +81,36 @@ The `name` is **optional** for the razor bundle tag helpers. If you don't define
</abp-style-bundle> </abp-style-bundle>
```` ````
This will potentially create **two different bundles** (one incudes the `my-global-style.css` and other does not). 这将潜在地创建**两个不同的bundles**(一个包括`my-global-style.css`而另一个则不包括).
Advantages of **unnamed** bundles: **未命名的** bundles优点:
* Can **conditionally add items** to the bundle. But this may lead to multiple variations of the bundle based on the conditions. * 可以**有条件地将项目**添加到捆绑包中. 但这可能会导致基于条件的捆绑的存在多种变化.
Advantages of **named** bundles: **命名** bundles优点:
* Other **modules can contribute** to the bundle by its name (see the sections below). * Other **modules can contribute** to the bundle by its name (see the sections below).
* 其他模块可以通过其名称为捆绑包做出贡献(参见下面的部分).
#### Single File #### 单个文件
If you need to just add a single file to the page, you can use the `abp-script` or `abp-style` tag without a wrapping in the `abp-script-bundle` or `abp-style-bundle` tag. Example: 如果你只需要在页面中添加一个文件, 你可以使用`abp-script`或`abp-style`而不需要包含在`abp-script-bundle`或`abp-style-bundle`中. 例:
````xml ````xml
<abp-script src="/scripts/my-script.js" /> <abp-script src="/scripts/my-script.js" />
```` ````
The bundle name will be *scripts.my-scripts* for the example above ("/" is replaced by "."). All bundling features are work as expected for single file bundles too. 对于上面的示例,包名称将是 *scripts.my-scripts*("/"替换为"."). 所有捆绑功能也可以按预期应用于单个文件.
### Bundling Options ### Bundling 选项
If you need to use same bundle in **multiple pages** or want to use some more **powerful features**, you can configure bundles **by code** in your [module](../Module-Development-Basics.md) class. If you need to use same bundle in **multiple pages** or want to use some more **powerful features**, you can configure bundles **by code** in your [module](../Module-Development-Basics.md) class.
#### Creating A New Bundle 如果你需要在 **多个页面中使用相同的包** 或想要使用更多 **强大功能**, 你可以在[模块](../Module-Development-Basics.md)类中进行**配置**.
#### 创建一个新的捆绑包
Example usage: 用法示例:
````C# ````C#
[DependsOn(typeof(AbpAspNetCoreMvcUiBundlingModule))] [DependsOn(typeof(AbpAspNetCoreMvcUiBundlingModule))]
@ -131,20 +135,20 @@ public class MyWebModule : AbpModule
} }
```` ````
> You can use the same name (*MyGlobalBundle* here) for a script & style bundle since they are added to different collections (`ScriptBundles` and `StyleBundles`). > 您可以在脚本和样式包中使用相同的名称(*MyGlobalBundle*), 因为它们被添加到不同的集合(`ScriptBundles`和`StyleBundles`).
After defining such a bundle, it can be included into a page using the same tag helpers defined above. Example: 在定义bundle之后, 可以使用上面定义的相同tag helpers将其包括在页面中. 例如:
````html ````html
<abp-script-bundle name="MyGlobalBundle" /> <abp-script-bundle name="MyGlobalBundle" />
```` ````
This time, no file defined in the tag helper definition because the bundle files are defined by the code. 这次tag helper定义中没有定义文件, 因为捆绑文件是由代码定义的.
#### Configuring An Existing Bundle #### 配置现有的 Bundle
ABP supports [modularity](../Module-Development-Basics.md) for bundling as well. A module can modify an existing bundle that is created by a dependant module. ABP也支持[模块化](../Module-Development-Basics.md)捆绑. 模块可以修改由依赖模块创建的捆绑包.
Example: 例如:
````C# ````C#
[DependsOn(typeof(MyWebModule))] [DependsOn(typeof(MyWebModule))]
@ -166,13 +170,13 @@ public class MyWebExtensionModule : AbpModule
} }
```` ````
> It's not possible to configure unnamed bundle tag helpers by code, because their name are not known at the development time. It's suggested to always use a name for a bundle tag helper. > 无法通过代码配置未命名的bundle tag helpers, 因为它们的名称在开发时是未知的. 建议始终使用bundle tag helper的名称.
### Bundle Contributors ### Bundle 贡献者
Adding files to an existing bundle seems useful. What if you need to **replace** a file in the bundle or you want to **conditionally** add files? Defining a bundle contributor provides extra power for such cases. 将文件添加到现有bundle似乎很有用. 如果你需要**替换**bundle中的文件或者你想**有条件地**添加文件怎么办? 定义bundle贡献者可为此类情况提供额外的功能.
An example bundle contributor that replaces bootstrap.css with a customized version: 一个bundle的贡献者使用自定义版本bootstrap.css替换示例:
````C# ````C#
public class MyExtensionGlobalStyleContributor : BundleContributor public class MyExtensionGlobalStyleContributor : BundleContributor
@ -187,7 +191,7 @@ public class MyExtensionGlobalStyleContributor : BundleContributor
} }
```` ````
Then you can use this contributor as below: 然后你可以按照下面的代码使用这个贡献者:
````C# ````C#
services.Configure<BundlingOptions>(options => services.Configure<BundlingOptions>(options =>
@ -200,8 +204,8 @@ services.Configure<BundlingOptions>(options =>
}); });
```` ````
Contributors can also be used in the bundle tag helpers. 贡献者也可以在bundle tag helpers中使用.
Example: 例如:
````xml ````xml
<abp-style-bundle> <abp-style-bundle>
@ -211,12 +215,12 @@ Example:
</abp-style-bundle> </abp-style-bundle>
```` ````
`abp-style` and `abp-script` tags can get `type` attributes (instead of `src` attributes) as shown in this sample. When you add a bundle contributor, its dependencies are also automatically added to the bundle. `abp-style`和`abp-script`标签可以使用`type`属性(而不是`src`属性), 如本示例所示. 添加bundle贡献者时, 其依赖关系也会自动添加到bundle中.
#### Contributor Dependencies #### Contributor Dependencies
A bundle contributor can have one or more dependencies to other contributors. bundle贡献者可以与其他贡献者具有一个或多个依赖关系.
Example: 例如:
````C# ````C#
[DependsOn(typeof(MyDependedBundleContributor))] //Define the dependency [DependsOn(typeof(MyDependedBundleContributor))] //Define the dependency
@ -226,19 +230,19 @@ public class MyExtensionStyleBundleContributor : BundleContributor
} }
```` ````
When a bundle contributor is added, its dependencies are **automatically and recursively** added. Dependencies added by the **dependency order** by preventing **duplicates**. Duplicates are prevented even if they are in separated bundles. ABP organizes all bundles in a page and eliminates duplications. 添加bundle贡献者时,其依赖关系将 **自动并递归** 添加. **依赖顺序** 通过阻止 **重复** 添加的依赖关系. 即使它们处于分离的束中,也会阻止重复. ABP在页面中组织所有bundle并消除重复.
Creating contributors and defining dependencies is a way of organizing bundle creation across different modules. 创建贡献者和定义依赖关系是一种跨不同模块组织包创建的方法.
#### Accessing to the IServiceProvider #### 访问 IServiceProvider
While it is rarely needed, `BundleConfigurationContext` has a `ServiceProvider` property that you can resolve service dependencies inside the `ConfigureBundle` method. 虽然很少需要它, 但是`BundleConfigurationContext`有一个`ServiceProvider`属性, 你可以在`ConfigureBundle`方法中解析服务依赖.
#### Standard Package Contributors #### 标准包装贡献者
Adding a specific NPM package resource (js, css files) into a bundle is pretty straight forward for that package. For example you always add the `bootstrap.css` file for the bootstrap NPM package. 将特定的NPM包资源(js,css文件)添加到包中对于该包非常简单. 例如, 你总是为bootstrap NPM包添加`bootstrap.css`文件.
There are built-in contributors for all [standard NPM packages](Client-Side-Package-Management.md). For example, if your contributor depends on the bootstrap, you can just declare it, instead of adding the bootstrap.css yourself. 所有[标准NPM包](Client-Side-Package-Management.md)都有内置的贡献者. 例如,如果你的贡献者依赖于引导程序,你可以声明它,而不是自己添加bootstrap.css.
````C# ````C#
[DependsOn(typeof(BootstrapStyleContributor))] //Define the bootstrap style dependency [DependsOn(typeof(BootstrapStyleContributor))] //Define the bootstrap style dependency
@ -248,25 +252,25 @@ public class MyExtensionStyleBundleContributor : BundleContributor
} }
```` ````
Using the built-in contributors for standard packages; 使用标准包的内置贡献者:
* Prevents you typing **invalid the resource paths**. * 防止你输入**无效的资源路径**.
* Prevents changing your contributor if the resource **path changes** (the dependant contributor will handle it). * 如果资源 **路径发生变化** (依赖贡献者将处理它),则防止更改你的贡献者.
* Prevents multiple modules adding the **duplicate the files**. * 防止多个模块添加**重复文件**.
* Manages **dependencies recursively** (adds dependencies of dependencies, if necessary). * 以递归方式管理依赖项(如果需要,添加依赖项的依赖项).
##### Volo.Abp.AspNetCore.Mvc.UI.Packages Package ##### Volo.Abp.AspNetCore.Mvc.UI.Packages 包
> This package is already installed by default in the startup templates. So, most of the time, you don't need to install it manually. > 默认情况下已在启动模板安装此软件包. 大多数情况下,你不需要手动安装它.
Standard package contributors are defined in the `Volo.Abp.AspNetCore.Mvc.UI.Packages` NuGet package. 标准包贡献者在`Volo.Abp.AspNetCore.Mvc.UI.Packages` NuGet包中定义.
To install it to your project: 安装到你的项目中:
```` ````
install-package Volo.Abp.AspNetCore.Mvc.UI.Packages install-package Volo.Abp.AspNetCore.Mvc.UI.Packages
```` ````
Then add the `AbpAspNetCoreMvcUiPackagesModule` module dependency to your own module; 然后将`AbpAspNetCoreMvcUiPackagesModule`模块依赖项添加到你的模块中;
````C# ````C#
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
@ -284,8 +288,8 @@ namespace MyCompany.MyProject
#### Bundle Inheritance #### Bundle Inheritance
In some specific cases, it may be needed to create a **new** bundle **inherited** from other bundle(s). Inheriting from a bundle (recursively) inherits all files/contributors of that bundle. Then the derived bundle can add or modify files/contributors **without modifying** the original bundle. 在某些特定情况下, 可能需要从其他bundle创建一个 **新** bundle **继承**, 从bundle继承(递归)继承该bundle的所有文件/贡献者. 然后派生的bundle可以添加或修改文件/贡献者**而无需修改**原始包.
Example: 例如:
````c# ````c#
services.Configure<BundlingOptions>(options => services.Configure<BundlingOptions>(options =>
@ -302,22 +306,22 @@ services.Configure<BundlingOptions>(options =>
}); });
```` ````
### Themes ### 主题
Themes uses the standard package contributors to add library resources to page layouts. Themes may also define some standard/global bundles, so any module can contribute to these standard/global bundles. See the [theming documentation](Theming.md) for more. 主题使用标准包贡献者将库资源添加到页面布局. 主题还可以定义一些标准/全局包, 因此任何模块都可以为这些标准/全局包做出贡献. 有关更多信息, 请参阅[主题文档](Theming.md).
### Best Practices & Suggestions ### 最佳实践 & 建议
It's suggested to define multiple bundles for an application, each one is used for different purposes. 建议为应用程序定义多个包, 每个包用于不同的目的.
* **Global bundle**: Global style/script bundles are included to every page in the application. Themes already defines global style & script bundles. Your module can contribute to them. * **全局包**: 应用程序中的每个页面都包含全局样式/脚本包. 主题已经定义了全局样式和脚本包. 您的模块可以为他们做出贡献.
* **Layout bundles**: This is a specific bundle to an individual layout. Only contains resources shared among all the pages use the layout. Use the bundling tag helpers to create the bundle as a good practice. * **布局包**: 这是针对单个布局的特定包. 仅包含在所有页面之间共享的资源使用布局. 使用bundling tag helpers创建捆绑包是一种很好的做法.
* **Module bundles**: For shared resources among the pages of an individual module. * **模块包**: 用于单个模块页面之间的共享资源.
* **Page bundles**: Specific bundles created for each page. Use the bundling tag helpers to create the bundle as a best practice. * **页面包**: 为每个页面创建的特定包. 使用bundling tag helpers创建捆绑包作为最佳实践.
Establish a balance between performance, network bandwidth usage and count of many bundles. 在性能,网络带宽使用和捆绑包的数量之间建立平衡.
### See Also ### 参见
* [Client Side Package Management](Client-Side-Package-Management.md) * [客户端包管理](Client-Side-Package-Management.md)
* [Theming](Theming.md) * [主题](Theming.md)

86
docs/zh-Hans/AspNetCore/Client-Side-Package-Management.md

@ -1,73 +1,73 @@
## ASP.NET Core MVC Client Side Package Management ## ASP.NET Core MVC 客户端包管理
ABP framework can work with any type of client side package management systems. You can even decide to use no package management system and manage your dependencies manually. ABP框架可以与任何类型的客户端包管理系统一起使用. 甚至你可以决定不使用包管理系统并手动管理依赖项.
However, ABP framework works best with **NPM/Yarn**. By default, built-in modules are configured to work with NPM/Yarn. 但是, ABP框架最适用于**NPM/Yarn**. 默认情况下,内置模块配置为与NPM/Yarn一起使用.
Finally, we suggest the [**Yarn**](https://yarnpkg.com/) over the NPM since it's faster, stable and also compatible with the NPM. 最后, 我们建议[**Yarn**](https://yarnpkg.com/)而不是NPM,因为它更快,更稳定并且与NPM兼容.
### @ABP NPM Packages ### @ABP NPM Packages
ABP is a modular platform. Every developer can create modules and the modules should work together in a **compatible** and **stable** state. ABP是一个模块化平台. 每个开发人员都可以创建模块, 模块应该在**兼容**和**稳定**状态下协同工作.
One challenge is the **versions of the dependant NPM packages**. What if two different modules use the same JavaScript library but its different (and potentially incompatible) versions. 一个挑战是依赖NPM包的**版本**. 如果两个不同的模块使用相同的JavaScript库但其不同(并且可能不兼容)的版本会怎样.
To solve the versioning problem, we created a **standard set of packages** those depends on some common third-party libraries. Some example packages are [@abp/jquery](https://www.npmjs.com/package/@abp/jquery), [@abp/bootstrap](https://www.npmjs.com/package/@abp/bootstrap) and [@abp/font-awesome](https://www.npmjs.com/package/@abp/font-awesome). You can see the **list of packages** from the [Github repository](https://github.com/volosoft/abp/tree/master/npm/packs). 为了解决版本问题, 我们创建了一套**标准包**, 这取决于一些常见的第三方库. 一些示例包是[@abp/jquery](https://www.npmjs.com/package/@abp/jquery), [@ abp/bootstrap](https://www.npmjs.com/package/@abp/bootstrap)和[@abp/font-awesome](https://www.npmjs.com/package/@abp/font-awesome). 你可以从[Github存储库](https://github.com/volosoft/abp/tree/master/npm/packs)中查看**列表**.
The benefit of a **standard package** is: **标准包**的好处是:
* It depends on a **standard version** of a package. Depending on this package is **safe** because all modules depend on the same version. * 它取决于包装的**标准版本**。 取决于此包是**安全**,因为所有模块都依赖于相同的版本。
* It contains the gulp task to copy library resources (js, css, img... files) from the **node_modules** folder to **wwwroot/libs** folder. See the *Mapping The Library Resources* section for more. * 它包含将库资源(js,css,img...文件)从**node_modules**文件夹复制到**wwwroot/libs**文件夹的gulp任务. 有关更多信息, 请参阅 *映射库资源* 部分.
Depending on a standard package is easy. Just add it to your **package.json** file like you normally do. Example:
依赖标准包装很容易. 只需像往常一样将它添加到**package.json**文件中. 例如:
````
{ {
... ...
"dependencies": { "dependencies": {
"@abp/bootstrap": "^1.0.0" "@abp/bootstrap": "^1.0.0"
} }
} }
````
建议依赖于标准软件包, 而不是直接依赖于第三方软件包.
It's suggested to depend on a standard package instead of directly depending on a third-party package. #### 安装包
#### Package Installation
After depending on a NPM package, all you should do is to run the **yarn** command from the command line to install all the packages and their dependencies: 依赖于NPM包后, 你应该做的就是从命令行运行**yarn**命令来安装所有包及其依赖项:
```` ````
yarn yarn
```` ````
Alternatively, you can use `npm install` but [Yarn](https://yarnpkg.com/) is suggested as mentioned before. 虽然你可以使用`npm install`,但如前所述,建议使用[Yarn](https://yarnpkg.com/).
#### Package Contribution #### 贡献包
If you need a third-party NPM package that is not in the standard set of packages, you can create a Pull Request on the Github [repository](https://github.com/volosoft/abp). A pull request that follows these rules is accepted: 如果你需要不在标准软件包中的第三方NPM软件包,您可以在Github[repository](https://github.com/volosoft/abp)上创建Pull请求. 接受遵循这些规则的拉取请求:
* Package name should be named as `@abp/package-name` for a `package-name` on NPM (example: `@abp/bootstrap` for the `bootstrap` package). * 对于NPM上的`package-name`, 包名称应该命名为`@abp/package-name`(例如:`bootstrap`包的`@abp/bootstrap`).
* It should be the **latest stable** version of the package. * 它应该是**最新的稳定**版本的包.
* It should only depend a **single** third-party package. It can depend on multiple `@abp/*` packages. * 它应该只依赖于**单个**第三方包. 它可以依赖于多个`@abp/*`包.
* The package should include a `abp.resourcemapping.js` file formatted as defined in the *Mapping The Library Resources* section. This file should only map resources for the depended package. * 包应包含一个`abp.resourcemapping.js`文件格式,如*映射库资源*部分中所定义. 此文件应仅映射所依赖包的资源.
* You also need to create [bundle contributor(s)](Bundling-Minification.md) for the package you have created. * 你还需要为你创建的包创建[bundle贡献者](Bundling-Minification.md).
See current standard packages for examples. 有关示例, 请参阅当前标准包.
### Mapping The Library Resources ### 映射库资源
Using NPM packages and NPM/Yarn tool is the de facto standard for client side libraries. NPM/Yarn tool creates a **node_modules** folder in the root folder of your web project. 使用NPM包和NPM/Yarn工具是客户端库的事实标准. NPM/Yarn工具在Web项目的根文件夹中创建一个**node_modules**文件夹.
Next challenge is copying needed resources (js, css, img... files) from the `node_modules` into a folder inside the **wwwroot** folder to make it accessible to the clients/browsers. 下一个挑战是将所需的资源(js,css,img ...文件)从`node_modules`复制到**wwwroot**文件夹内的文件夹中,以使其可供客户端/浏览器访问.
ABP defines a [Gulp](https://gulpjs.com/) based task to **copy resources** from **node_modules** to **wwwroot/libs** folder. Each **standard package** (see the *@ABP NPM Packages* section) defines the mapping for its own files. So, most of the time, you only configure dependencies. ABP将基于[Gulp](https://gulpjs.com/)的任务定义为**将资源**从**node_modules**复制到**wwwroot/libs**文件夹. 每个**标准包**(参见*@ABP NPM Packages*部分)定义了自己文件的映射. 因此, 大多数情况你只配置依赖项.
The **startup templates** are already configured to work all these out of the box. This section will explain the configuration options. **启动模板**已经配置为开箱即用的所有这些。 本节将介绍配置选项。
#### Resource Mapping Definition File #### 资源映射定义文件
A module should define a JavaScript file named `abp.resourcemapping.js` which is formatted as in the example below: 模块应该定义一个名为`abp.resourcemapping.js`的JavaScript文件,其格式如下例所示:
````js ````js
module.exports = { module.exports = {
@ -84,11 +84,11 @@ module.exports = {
} }
```` ````
* **aliases** section defines standard aliases (placeholders) that can be used in the mapping paths. **@node_modules** and **@libs** are required (by the standard packages), you can define your own aliases to reduce duplication. * **aliases**部分定义了可在映射路径中使用的标准别名(占位符). **@node_modules**和 **@libs**是必需的(通过标准包), 你可以定义自己的别名以减少重复.
* **clean** section is a list of folders to clean before copying the files. * **clean**部分是在复制文件之前要清理的文件夹列表.
* **mappings** section is a list of mappings of files/folders to copy. This example does not copy any resource itself, but depends on a standard package. * **mappings**部分是要复制的文件/文件夹的映射列表.此示例不会复制任何资源本身,但取决于标准包.
An example mapping configuration is shown below: 示例映射配置如下所示:
````js ````js
mappings: { mappings: {
@ -97,19 +97,19 @@ mappings: {
} }
```` ````
#### Using The Gulp #### 使用 Gulp
Once you properly configure the `abp.resourcemapping.js` file, you can run the gulp command from the command line: 正确配置`abp.resourcemapping.js`文件后, 可以从命令行运行gulp命令:
```` ````
gulp gulp
```` ````
When you run the `gulp`, all packages will copy their own resources into the **wwwroot/libs** folder. Running `yarn & gulp` is only necessary if you make a change in your dependencies in the **package.json** file. 当你运行`gulp`时,所有包都会将自己的资源复制到**wwwroot/libs**文件夹中. 只有在**package.json**文件中对依赖项进行更改时, 才需要运行`yarn&gulp`.
> When you run the Gulp command, dependencies of the application are resolved using the package.json file. The Gulp task automatically discovers and maps all resources from all dependencies (recursively). > 运行Gulp命令时, 使用package.json文件解析应用程序的依赖关系. Gulp任务自动发现并映射来自所有依赖项的所有资源(递归).
#### See Also #### 参见
* [Bundling & Minification](Bundling-Minification.md) * [捆绑 & 压缩](Bundling-Minification.md)
* [Theming](Theming.md) * [主题](Theming.md)

176
docs/zh-Hans/Best-Practices/Application-Services.md

@ -1,29 +1,29 @@
## Application Services Best Practices & Conventions ## 应用服务最佳实践 & 约定
* **Do** create an application service for each **aggregate root**. * **推荐** 为每个 **聚合根** 创建一个应用服务.
### Application Service Interface ### 应用服务接口
* **Do** define an `interface` for each application service in the **application contracts** package. * **推荐** 在 **application.contracts**层中为每一个应用服务定义一个`接口`.
* **Do** inherit from the `IApplicationService` interface. * **推荐** 继承 `IApplicationService` 接口 .
* **Do** use the `AppService` postfix for the interface name (ex: `IProductAppService`). * **推荐** 接口名称使用`AppService` 后缀 (如: `IProductAppService`).
* **Do** create DTOs (Data Transfer Objects) for inputs and outputs of the service. * **推荐** 为服务创建输入输出DTO(数据传输对象).
* **Do not** get/return entities for the service methods. * **不推荐** 服务中含有返回实体的方法.
* **Do** define DTOs based on the [DTO best practices](Data-Transfer-Objects.md). * **推荐** 根据[DTO 最佳实践](Data-Transfer-Objects.md)定义DTO.
#### Outputs #### 输出
* **Avoid** to define too many output DTOs for same or related entities. Instead, define a **basic** and a **detailed** DTO for an entity. * **避免** 为相同或相关实体定义过多的输出DTO. 为实体定义 **基础** 和 **详细** DTO.
##### Basic DTO ##### 基础DTO
**Do** define a **basic** DTO for an entity. **推荐** 为实体定义一个**基础**DTO.
- Include all the **primitive properties** directly on the entity. - 直接包含实体中所有的**原始属性**.
- Exception: Can **exclude** properties for **security** reasons (like User.Password). - 例外: 出于**安全**原因,可以**排除**某些属性(像 `User.Password`).
- Include all the **sub collections** of the entity where every item in the collection is a simple **relation DTO**. - 包含实体中所有**子集合**, 每个集合项都是一个简单的**关系DTO**.
Example: 示例:
```c# ```c#
public class IssueDto : FullAuditedEntityDto<Guid> public class IssueDto : FullAuditedEntityDto<Guid>
@ -41,17 +41,17 @@ public class IssueLabelDto
} }
``` ```
##### Detailed DTO ##### 详细DTO
**Do** define a **detailed** DTO for an entity if it has reference(s) to other aggregate roots. **Do** 如果实体持有对其他聚合根的引用,那么应该为其定义**详细**DTO.
* Include all the **primitive properties** directly on the entity. * 直接包含实体中所有的 **原始属性**.
- Exception-1: Can **exclude** properties for **security** reasons (like `User.Password`). - 例外-1: 出于**安全**原因,可以**排除**某些属性(像 `User.Password`).
- Exception-2: **Do** exclude reference properties (like `MilestoneId` in the example above). Will already add details for the reference properties. - 例外-2: **推荐** 排除引用属性(如上例中的 `MilestoneId`). 为其添加引用属性的详细信息.
* Include a **basic DTO** property for every reference property. * 为每个引用属性添加其**基本DTO** .
* Include all the **sub collections** of the entity where every item in the collection is the **basic DTO** of the related entity. * 包含实体的**所有子集合**, 集合中的每项都是相关实体的基本DTO.
Example: 示例:
````C# ````C#
public class IssueWithDetailsDto : FullAuditedEntityDto<Guid> public class IssueWithDetailsDto : FullAuditedEntityDto<Guid>
@ -75,58 +75,57 @@ public class LabelDto : EntityDto<Guid>
} }
```` ````
#### Inputs #### 输入
* **Do not** define any property in an input DTO that is not used in the service class. * **不推荐** 在输入DTO中定义未在服务类中使用的属性.
* **Do not** share input DTOs between application service methods. * **不推荐** 在应用服务方法之间共享输入DTO.
* **Do not** inherit an input DTO class from another one. * **不推荐** 继承另一个输入DTO类.
* **May** inherit from an abstract base DTO class and share some properties between different DTOs in that way. However, should be very careful in that case because manipulating the base DTO would effect all related DTOs and service methods. Avoid from that as a good practice. * **可以** 继承自抽象基础DTO类, 并以这种方式在不同的DTO之间共享一些属性. 但是在这种情况下需要非常小心, 因为更新基础DTO会影响所有相关的DTO和服务方法. 所以避免这样做是一种好习惯.
#### Methods #### 方法
* **Do** define service methods as asynchronous with **Async** postfix. * **推荐** 为异步方法使用 **Async** 后缀.
* **Do not** repeat the entity name in the method names. * **不推荐** 在方法名中重复实体的名称.
* Example: Define `GetAsync(...)` instead of `GetProductAsync(...)` in the `IProductAppService`. * 例如: 在 `IProductAppService` 中定义`GetProductAsync(...)` 而不是 `GetAsync(...)` .
##### Getting A Single Entity ##### 获取单一实体
* **Do** use the `GetAsync` **method name**. * **推荐** 使用 `GetAsync` 做为**方法名**.
* **Do** get Id with a **primitive** method parameter. * **推荐** 使用id做为方法参数.
* Return the **detailed DTO**. Example: * 返回 **详细DTO**. 示例:
````C# ````C#
Task<QuestionWithDetailsDto> GetAsync(Guid id); Task<QuestionWithDetailsDto> GetAsync(Guid id);
```` ````
##### Getting A List Of Entities ##### 获取实体集合
* **Do** use the `GetListAsync` **method name**. * **推荐** 使用 `GetListAsync` 做为**方法名**.
* **Do** get a single DTO argument for **filtering**, **sorting** and **paging** if necessary. * **推荐** 如果需要获取单个DTO可以使用参数进行 **过滤**, **排序** 和 **分页**.
* **Do** implement filters optional where possible. * **推荐** 尽可能让过滤参数可选.
* **Do** implement sorting & paging properties as optional and provide default values. * **推荐** 将排序与分页属性设置为可选, 并且提供默认值.
* **Do** limit maximum page size (for performance reasons). * **推荐** 限制最大页数大小 (基本性能考虑).
* **Do** return a list of **detailed DTO**s. Example: * **推荐** 返回 **详细DTO**集合. 示例:
````C# ````C#
Task<List<QuestionWithDetailsDto>> GetListAsync(QuestionListQueryDto queryDto); Task<List<QuestionWithDetailsDto>> GetListAsync(QuestionListQueryDto queryDto);
```` ````
##### Creating A New Entity ##### 创建一个新实体
* **Do** use the `CreateAsync` **method name**. * **推荐** 使用 `CreateAsync` 做为**方法名**.
* **Do** get a **specialized input** DTO to create the entity. * **推荐** 使用**专门的输入DTO**来创建实体.
* **Do** use **data annotations** for input validation. * **推荐** 使用 **data annotations** 进行输入验证.
* Share constants between domain wherever possible (via constants defined in the **domain shared** package). * 尽可能在**领域**之间共享常量(通过域**domain shared** package定义的常量).
* **Do** return **the detailed** DTO for new created entity. * **推荐** 只需要创建实体的**最少**信息, 但是提供了其他可选属性.
* **Do** only require the **minimum** info to create the entity but provide possibility to set others as optional properties.
Example **method**: 示例**方法**:
````C# ````C#
Task<QuestionWithDetailsDto> CreateAsync(CreateQuestionDto questionDto); Task<QuestionWithDetailsDto> CreateAsync(CreateQuestionDto questionDto);
```` ````
The related **DTO**: 输入**DTO**:
````C# ````C#
public class CreateQuestionDto public class CreateQuestionDto
@ -142,68 +141,65 @@ public class CreateQuestionDto
} }
```` ````
##### Updating An Existing Entity ##### 更新已存在的实体
- **Do** use the `UpdateAsync` **method name**. - **推荐** 使用 `UpdateAsync` 做为**方法名**.
- **Do** get a **specialized input** DTO to update the entity. - **推荐** 使用**专门的输入DTO**来更新实体.
- **Do** get the Id of the entity as a separated primitive parameter. Do not include to the update DTO. - **推荐** 获取实体的id做为分离的原始参数. 不要包含更新DTO.
- **Do** use **data annotations** for input validation. - **推荐** 使用 **data annotations** 进行输入验证.
- Share constants between domain wherever possible (via constants defined in the **domain shared** package). - 尽可能在**领域**之间共享常量(通过域**domain shared** package定义的常量).
- **Do** return **the detailed** DTO for the updated entity. - **推荐** 返回更新实体的**详细**DTO.
Example: 示例:
````C# ````C#
Task<QuestionWithDetailsDto> UpdateAsync(Guid id, UpdateQuestionDto updateQuestionDto); Task<QuestionWithDetailsDto> UpdateAsync(Guid id, UpdateQuestionDto updateQuestionDto);
```` ````
##### Deleting An Existing Entity ##### 删除已存在的实体
- **Do** use the `DeleteAsync` **method name**. - **推荐** 使用 `DeleteAsync` 做为**方法名**.
- **Do** get Id with a **primitive** method parameter. Example: - **推荐** 使用原始参数 id. 示例:
````C# ````C#
Task DeleteAsync(Guid id); Task DeleteAsync(Guid id);
```` ````
##### Other Methods ##### 其他方法
* **Can** define additional methods to perform operations on the entity. Example: * **可以** 定义其他方法以对实体执行操作. 示例:
````C# ````C#
Task<int> VoteAsync(Guid id, VoteType type); Task<int> VoteAsync(Guid id, VoteType type);
```` ````
This method votes a question and returns the current score of the question. 此方法为试题投票并返回试题的当前分数.
### Application Service Implementation ### 应用服务实现
* **Do** develop the application layer **completely independent from the web layer**. * **推荐** 开发**完全独立于web层**的应用层.
* **Do** implement application service interfaces in the **application layer**. * **推荐** 在**应用层**实现应用服务接口.
* **Do** use the naming convention. Ex: Create `ProductAppService` class for the `IProductAppService` interface. * **推荐** 使用命名约定. 如: 为 `IProductAppService` 接口创建 `ProductAppService` 类.
* **Do** inherit from the `ApplicationService` base class. * **推荐** 继承自 `ApplicationService` 基类.
#### Using Repositories #### 使用仓储
* **Do** use the specifically designed repositories (like `IProductRepository`). * **推荐** 使用专门设计的仓储 (如 `IProductRepository`).
* **Do not** use generic repositories (like `IRepository<Product>`). * **不推荐** 使用泛型仓储 (如 `IRepository<Product>`).
#### Querying Data #### 查询数据
* **Do not** use LINQ/SQL for querying data from database inside the application service methods. It's repository's responsibility to perform LINQ/SQL queries from the data source. * **不推荐** 在应用程序服务方法中使用linq/sql查询来自数据库的数据. 让仓储负责从数据源执行linq/sql查询.
#### Manipulating / Deleting Entities #### 操作/删除 实体
* **Do** always get all the related entities from repositories to perform the operations on them.
#### Using Other Application Services
* **Do not** use other application services of the same module/application. Instead;
* Use domain layer to perform the required task.
* Extract a new class and share between the application services to accomplish the code reuse when necessary.
* **Can** use application services of others only if;
* They are parts of another module / microservice.
* The current module has only reference to the application contracts of the used module.
* **推荐** 总是从数据库中获取所有的相关实体以对他们执行操作.
#### 使用其他应用服务
* **不推荐** 使用相同 **模块/应用程序** 的其他应用服务. 相反;
* 使用领域层执行所需的任务.
* 提取新类并在应用程序服务之间共享, 在必要时代码重用.
* **可以** 在以下情况下使用其他应用服务;
* 它们是另一个模块/微服务的一部分.
* 当前模块仅引用已使用模块的application contracts.

2
docs/zh-Hans/Best-Practices/Data-Transfer-Objects.md

@ -1,4 +1,4 @@
## 数据传输对象最佳实践&约定 ## 数据传输对象最佳实践 & 约定
* **推荐** 在 **application.contracts** 层中定义DTO. * **推荐** 在 **application.contracts** 层中定义DTO.
* **推荐** 在可能和必要的情况下从预构建的 **基础DTO类** 继承 (如 `EntityDto<TKey>`, `CreationAuditedEntityDto<TKey>`, `AuditedEntityDto<TKey>`, `FullAuditedEntityDto<TKey>` 等). * **推荐** 在可能和必要的情况下从预构建的 **基础DTO类** 继承 (如 `EntityDto<TKey>`, `CreationAuditedEntityDto<TKey>`, `AuditedEntityDto<TKey>`, `FullAuditedEntityDto<TKey>` 等).

2
docs/zh-Hans/Best-Practices/Domain-Services.md

@ -1,3 +1,3 @@
## Domain Services Best Practices & Conventions ## 领域服务最佳实践 & 约定
TODO TODO

112
docs/zh-Hans/Best-Practices/Entities.md

@ -1,98 +1,98 @@
## Entity Best Practices & Conventions ## 实体最佳实践 & 约定
### Entities ### 实体
Every aggregate root is also an entity. So, these rules are valid for aggregate roots too unless aggregate root rules override them. 每个聚合根也是一个实体, 所以这些规则对聚合根也是有效的, 除非聚合根的某些规则覆盖了它们.
- **Do** define entities in the **domain layer**. - **推荐** 在 **领域层** 中定义实体.
#### Primary Constructor #### 主构造函数
* **Do** define a **primary constructor** that ensures the validity of the entity on creation. Primary constructors are used to create a new instance of the entity by the application code. * **推荐** 定义一个 **主构造函数** 确保实体在创建时的有效性, 在代码中通过主构造函数创建实体的新实例.
- **Do** define primary constructor as `public`, `internal` or `protected internal` based on the requirements. If it's not public, the entity is expected to be created by a domain service. - **推荐** 根据需求把主构造函数定义为 `public`,`internal` 或 `protected internal` . 如果它不是public的, 那么应该由领域服务来创建实体.
- **Do** always initialize sub collections in the primary constructor. - **推荐** 总是在主构造函数中初始化子集合.
- **Do not** generate `Guid` keys inside the constructor. Get it as a parameter, so the calling code will use `IGuidGenerator` to generate a new `Guid` value. - **不推荐** 在主构造函数中生成 `Guid` 键, 应该将其做为参数获取, 在调用时推荐使用 `IGuidGenerator` 生成新的 `Guid` 值做为参数.
#### Parameterless Constructor #### 无参构造函数
- **Do** always define a `protected` parameterless constructor to be compatible with ORMs. - **推荐** 总是定义 `protected` 无参构造函数与ORM兼容.
#### References #### 引用
- **Do** always **reference** to other aggregate roots **by Id**. Never add navigation properties to other aggregate roots. - **推荐** 总是通过 **id** **引用** 其他聚合根, 不要将导航属性添加到其他聚合根中.
#### Other Class Members #### 类的其他成员
- **Do** always define properties and methods as `virtual` (except `private` methods, obviously). Because some ORMs and dynamic proxy tools require it. - **推荐** 总是将属性与方法定义为 `virtual` (除了`私有`方法 ). 因为有些ORM和动态代理工具需要.
- **Do** keep the entity as always **valid** and **consistent** within its own boundary. - **推荐** 保持实体在自身边界内始终 **有效** 和 **一致**.
- **Do** define properties with `private`, `protected`, `internal ` or `protected internal` setter where it is needed to protect the entity consistency and validity. - **推荐** 使用 `private`,`protected`,`internal`或`protected internal` setter定义属性, 保护实体的一致性和有效性.
- **Do** define `public `, `internal` or `protected internal` (virtual) **methods** to change the properties (with non-public setters) if necessary. - **推荐** 定义 `public`, `internal` 或 `protected internal` (virtual)**方法**在必要时更改属性值(使用非public setters时).
### Aggregate Roots ### 聚合根
#### Primary Keys #### 主键
* **Do** always use a **Id** property for the aggregate root key. * **推荐** 总是使用 **Id** 属性做为聚合根主键.
* **Do not** use **composite keys** for aggregate roots. * **不推荐** 在聚合根中使用 **复合主键**.
* **Do** use **Guid** as the **primary key** of all aggregate roots. * **推荐** 所有的聚合根都使用 **Guid** 类型 **主键**.
#### Base Class #### 基类
* **Do** inherit from the `AggregateRoot<TKey>` or one of the audited classes (`CreationAuditedAggregateRoot<TKey>`, `AuditedAggregateRoot<TKey>` or `FullAuditedAggregateRoot<TKey>`) based on requirements. * **推荐** 根据需求继承 `AggregateRoot<TKey>` 或以下一个审计类 (`CreationAuditedAggregateRoot<TKey>`, `AuditedAggregateRoot<TKey>` 或 `FullAuditedAggregateRoot<TKey>`).
#### Aggregate Boundary #### 聚合边界
* **Do** keep aggregates **as small as possible**. Most of the aggregates will only have primitive properties and will not have sub collections. Consider these as design decisions: * **推荐** 聚合**尽可能小**. 大多数聚合只有原始属性, 不会有子集合. 把这些视为设计决策:
* **Performance** & **memory** cost of loading & saving aggregates (keep in mind that an aggregate is normally loaded & saved as a single unit). Larger aggregates will consume more CPU & memory. * 加载和保存聚合的 **性能** 与 **内存** 成本 (请记住,聚合通常是做为一个单独的单元被加载和保存的). 较大的聚合会消耗更多的CPU和内存.
* **Consistency** & **validity** boundary. * **一致性** & **有效性** 边界.
### Example ### 示例
#### Aggregate Root #### 聚合根
````C# ````C#
public class Issue : FullAuditedAggregateRoot<Guid> //Using Guid as the key/identifier public class Issue : FullAuditedAggregateRoot<Guid> //使用Guid作为键/标识符
{ {
public virtual string Title { get; private set; } //Changed using the SetTitle() method public virtual string Title { get; private set; } //使用 SetTitle() 方法set
public virtual string Text { get; set; } //Can be directly changed. null values are allowed public virtual string Text { get; set; } //可以直接set,null值也是允许的
public virtual Guid? MilestoneId { get; set; } //Reference to another aggregate root public virtual Guid? MilestoneId { get; set; } //引用其他聚合根
public virtual bool IsClosed { get; private set; } public virtual bool IsClosed { get; private set; }
public virtual IssueCloseReason? CloseReason { get; private set; } //Just an enum type public virtual IssueCloseReason? CloseReason { get; private set; } //一个枚举类型
public virtual Collection<IssueLabel> Labels { get; protected set; } //Sub collection public virtual Collection<IssueLabel> Labels { get; protected set; } //子集合
protected Issue() protected Issue()
{ {
/* This conctructor is for ORMs to be used while getting the entity from database. /* 此构造函数是提供给ORM用来从数据库中获取实体.
* - No need to initialize the Labels collection * - 无需初始化Lanels集合
since it will be overrided from the database. 因为它会被来自数据库的值覆盖.
- It's protected since proxying and deserialization tools - It's protected since proxying and deserialization tools
may not work with private constructors. 可能不适用于私有构造函数.
*/ */
} }
//Primary constructor //主构造函数
public Issue( public Issue(
Guid id, //Get Guid value from the calling code Guid id, //从调用代码中获取Guid值
[NotNull] string title, //Indicate that the title can not be null. [NotNull] string title, //表示标题不能为空.
string text = null, string text = null,
Guid? milestoneId = null) //Optional argument Guid? milestoneId = null) //可选参数
{ {
Id = id; Id = id;
Title = Check.NotNullOrWhiteSpace(title, nameof(title)); //Validate Title = Check.NotNullOrWhiteSpace(title, nameof(title)); //验证
Text = text; Text = text;
MilestoneId = milestoneId; MilestoneId = milestoneId;
Labels = new Collection<IssueLabel>(); //Always initialize the collection Labels = new Collection<IssueLabel>(); //总是初始化子集合
} }
public virtual void SetTitle([NotNull] string title) public virtual void SetTitle([NotNull] string title)
{ {
Title = Check.NotNullOrWhiteSpace(title, nameof(title)); //Validate Title = Check.NotNullOrWhiteSpace(title, nameof(title)); //验证
} }
/* AddLabel & RemoveLabel methods manages the Labels collection /* AddLabel和RemoveLabel方法管理Labels集合
* in a safe way (prevents adding the same label twice) */ * 安全的方式(防止两次添加相同的标签) */
public virtual void AddLabel(Guid labelId) public virtual void AddLabel(Guid labelId)
{ {
@ -109,8 +109,8 @@ public class Issue : FullAuditedAggregateRoot<Guid> //Using Guid as the key/iden
Labels.RemoveAll(l => l.LabelId == labelId); Labels.RemoveAll(l => l.LabelId == labelId);
} }
/* Close & ReOpen methods protect the consistency /* Close和ReOpen方法可保护一致性
* of the IsClosed and the CloseReason properties. */ * IsClosed 与 CloseReason 属性. */
public virtual void Close(IssueCloseReason reason) public virtual void Close(IssueCloseReason reason)
{ {
@ -126,7 +126,7 @@ public class Issue : FullAuditedAggregateRoot<Guid> //Using Guid as the key/iden
} }
```` ````
#### The Entity #### 实体
````C# ````C#
public class IssueLabel : Entity public class IssueLabel : Entity
@ -136,7 +136,7 @@ public class IssueLabel : Entity
protected IssueLabel() protected IssueLabel()
{ {
} }
public IssueLabel(Guid issueId, Guid labelId) public IssueLabel(Guid issueId, Guid labelId)
@ -147,7 +147,7 @@ public class IssueLabel : Entity
} }
```` ````
### References ### 参考文献
* Effective Aggregate Design by Vaughn Vernon * Effective Aggregate Design by Vaughn Vernon
http://dddcommunity.org/library/vernon_2011 http://dddcommunity.org/library/vernon_2011

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

@ -73,7 +73,7 @@ protected override void OnModelCreating(ModelBuilder builder)
} }
```` ````
- **不推荐** 直接在 `OnModelCreating` 方法中配置model,而是为 `ModelBuilder` 定义一个 **扩展方法**. 使用Configure*ModuleName*作为方法名称. 例如: - **不推荐** 直接在 `OnModelCreating` 方法中配置model, 而是为 `ModelBuilder` 定义一个 **扩展方法**. 使用Configure*ModuleName*作为方法名称. 例如:
````C# ````C#
public static class IdentityDbContextModelBuilderExtensions public static class IdentityDbContextModelBuilderExtensions

164
docs/zh-Hans/Best-Practices/Module-Architecture.md

@ -1,89 +1,89 @@
## Module Architecture Best Practices & Conventions ## 模块化架构最佳实践 & 约定
### Solution Structure ### 解决方案结构
* **Do** create a separated Visual Studio solution for every module. * **推荐** 在Visual Sudio中为每个模块创建一个单独的解决方案.
* **Do** name the solution as *CompanyName.ModuleName* (for core ABP modules, it's *Volo.Abp.ModuleName*). * **推荐** 将解决方案命名为*CompanyName.ModuleName*(对于ABP核心模块来说,它的命名方式是*Volo.Abp.ModuleName*).
* **Do** develop the module as layered, so it has several packages (projects) those are related to each other. * **推荐** 一个模块做为分层项目开发,因为它有几个包(项目)是相互关联的.
* Every package has its own module definition file and explicitly declares the dependencies for the depended packages/modules. * 每个包都有自己的模块定义文件,并显式声明所依赖的包/模块的依赖关系.
### Layers & Packages ### 层(layers) & 包(packages)
The following diagram shows the packages of a well-layered module and dependencies of those packages between them: 下面展示了一个分层良好的模块中的包以及它们之间的依赖关系:
![module-layers-and-packages](../images/module-layers-and-packages.jpg) ![module-layers-and-packages](../images/module-layers-and-packages.jpg)
The ultimate goal is to allow an application to use the module in a flexible manner. Example applications: 最终的目地是让应用程序以灵活的方式使用该模块. 示例应用程序:
* **A)** A **monolithic** application; * **A)** **单体**应用程序;
* Adds references to the **Web** and the **Application** packages. * 添加对**Web**和**Application**包的引用.
* Adds a reference to one of the **EF Core** or the **MongoDB** packages based on the preference. * 根据需要添加对**EF Core** 或 **MongoDB** 包的引用.
* The result; * 效果;
* The application **can show UI** of the module. * 应用程序可以显示**模块的UI**.
* It hosts the **application** and **domain** layers in the **same process** (that's why it needs to have a reference to a database integration package). * 它在**同一进程**中托管**应用层**和**领域层** (这就是为什么它引用对数据库集成包).
* This application also **serves** the module's **HTTP API** (since it includes the HttpApi package through the Web package). * 此应用程序还提供了模块的**HTTP API**(因为它通过Web包引用了HttpApi包).
* **B)** An application that just serves the module as a **microservice**; * **B)** 仅为**微服务**提供模块的应用程序;
* Adds a reference to **HttpApi** and **Application** packages. * 添加对**HttpApi**和**Application**包的引用.
* Adds a reference to one of the **EF Core** or the **MongoDB** packages based on the preference. * 根据需要添加对**EF Core** 或 **MongoDB** 包的引用.
* The result; * 效果;
* The application **can not show UI** of the module since it does not have a reference to the Web package. * 应用程序**无法显示模块的UI**, 因为它没有对Web包的引用.
* It hosts the **application** and **domain** layers in the **same process** (that's why it needs to have a reference to a database integration package). * 它在**同一进程**中托管**应用层**和**领域层** (这就是为什么它引用对数据库集成包).
* This application **serves** the module's **HTTP API** (as the main goal of the application). * 此应用程序提供了模块的**HTTP API**(它的主要目标).
* **C)** An application that shows the module **UI** but does not host the application (just uses it as a remote service that is hosted by the application A or B); * **C)** 显示模块**UI**但是不托管应用层的应用程序(仅将其用作由应用程序A或B托管的远程服务)
* Adds a reference to the **Web** and the **HttpApi.Client** packages. * 添加对**Web**和**HttpApi.Client**包的引用.
* Configures the remote endpoint for the HttpApi.Client package. * 配置HttpApi.Client包的远程端点.
* The result; * 效果;
* The application **can show UI** of the module. * 应用程序可以显示**模块的UI**.
* It does not host the application and domain layers of the module in the same process. Instead, uses it as a **remote service**. * 它不会在同一进程中托管模块的应用层和领域层. 而是将其用作**远程服务**.
* This application also **serves** the module's **HTTP API** (since it includes the HttpApi package through the Web package). * 此应用程序还提供了模块的**HTTP API**(因为它通过Web包引用了HttpApi包).
* **D)** A **client** application (or microservice) that just uses the module as a remote service (that is hosted by the application A, B or C); * **D)** **客户端**应用程序 (或微服务) 只使用模块作为远程服务(由应用程序A,B或C托管);
* Adds a reference to the **HttpApi.Client** package. * 添加对**HttpApi.Client**包的引用.
* Configures the remote endpoint for the HttpApi.Client package. * 配置HttpApi.Client包的远程端点.
* The result; * 效果;
* The application can use all the functionality of the module as a **remote client**. * 应用程序可以使用模块的所有功能作为**远程客户端**.
* The application is just a client and **can not serve** the **HTTP API** of the module. * 应用程序只是一个客户端,**无法提供**模块的**HTTP API**.
* The application is just a client and **can not show** the **UI** of the module. * 应用程序只是一个客户端,**无法显示**模块的**UI**.
* **E**) A proxy application that hosts the HTTP API of the module but just forwards all requests to another application (that is hosted by the application A, B or C); * **E**) 托管模块的HTTP API但只是将所有请求转发给另一个应用程序的代理应用程序 (由应用程序A, B或C托管);
* Adds a reference to the **HttpApi** and **HttpApi.Client** packages. * 添加对**HttpApi**和**HttpApi.Client**包的引用.
* Configures the remote endpoint for the HttpApi.Client package. * 配置HttpApi.Client包的远程端点.
* The result; * 效果;
* The application can use all the functionality of the module as a **remote client**. * 应用程序可以将模块的所有功能用作**远程客户端**.
* This application also **serves** the module's **HTTP API**, but actually works just like a proxy by redirecting all requests (for the module) to another remote server. * 应用程序也服务于模块的**HTTP API**, 但实际上它的工作方式与代理一样,将所有请求(模块)重定向到另一个远程服务器.
Next section describes the packages in more details. 下一节将详细地介绍这些包.
#### Domain Layer #### 领域层
* **Do** divide the domain layer into two projects: * **推荐** 将领域层划分为两个项目:
* **Domain.Shared** package, named as *CompanyName.ModuleName.Domain.Shared*, that contains constants, enums and other types those can be safely shared with the all layers of the module. This package can also be shared to 3rd-party clients. It can not contain entities, repositories, domain services or any other business objects. * **Domain.Shared** 包(项目) 命名为*CompanyName.ModuleName.Domain.Shared*,包含常量,枚举和其他类型, 它不能包含实体,存储库,域服务或任何其他业务对象. 可以安全地与模块中的所有层使用. 此包也可以与第三方客户端使用.
* **Domain** package, named as *CompanyName.ModuleName.Domain*, that contains entities, repository interfaces, domain service interfaces and their implementations and other domain objects. * **Domain** 包(项目) 命名为*CompanyName.ModuleName.Domain*, 包含实体, 仓储接口,领域服务接口及其实现和其他领域对象.
* Domain package depends on the **Domain.Share** package. * Domain package 依赖于 **Domain.Share** package.
#### Application Layer #### 应用服务层
* **Do** divide the application layer into two projects: * **推荐** 将应用服务层划分为两个项目:
* **Application.Contracts** package, named as *CompanyName.ModuleName.Application.Contracts*, that contains application service interfaces and related data transfer objects. * **Application.Contracts** 包(项目) 命名为*CompanyName.ModuleName.Application.Contracts,包含应用服务接口和相关的数据传输对象(DTO).
* Application contract package depends on the **Domain.Shared** package. * Application contract package 依赖于 **Domain.Shared** package.
* **Application** package, named as *CompanyName.ModuleName.Application*, that contains application service implementations. * **Application** 包(项目)命名为*CompanyName.ModuleName.Application*,包含应用服务实现.
* Application package depends on the **Domain** and the **Application.Contracts** packages. * Application package 依赖于 **Domain** 和 **Application.Contracts** packages.
#### Infrastructure Layer #### 基础设施层
* **Do** create a separated integration package for each ORM/database integration like Entity Framework Core and MongoDB. * **推荐** 为每个orm/数据库集成创建一个独立的集成包, 比如Entity Framework Core 和 MongoDB.
* **Do**, for instance, create a *CompanyName.ModuleName.EntityFrameworkCore* package that abstracts the Entity Framework Core integration. ORM integration packages depend on the **Domain** package. * **推荐** 例如, 创建一个抽象Entity Framework Core集成的*CompanyName.ModuleName.EntityFrameworkCore* package. ORM 集成 package 依赖于 **Domain** package.
* **Do not** depend on other layers from the ORM/database integration package. * **不推荐** 依赖于orm/数据库集成包中的其他层.
* **Do** create a separated integration package for each major library that is planned to be replaceable by another library without effecting the other packages. * **推荐** 为每个主要的库创建一个独立的集成包, 在不影响其他包的情况下可以被另一个库替换.
#### HTTP Layer #### HTTP 层
* **Do** create an **HTTP API** package, named as *CompanyName.ModuleName.HttpApi*, to develop a REST style HTTP API for the module. * **推荐** 创建命名为*CompanyName.ModuleName.HttpApi*的**HTTP API**包, 为模块开发REST风格的HTTP API.
* HTTP API package only depends on the **Application.Contracts** package. It does not depend on the Application package. * HTTP API package 只依赖于 **Application.Contracts** package. 不要依赖 Application package.
* **Do** create a Controller for each application service (generally by implementing their interfaces). These controllers uses the application service interfaces to delegate the actions. It just configures routes, HTTP methods and other web related stuffs if needed. * **推荐** 为每个应用服务创建一个Controller (通常通过实现其接口). 这些控制器使用应用服务接口来委托操作. 它根据需要配置路由, HTTP方法和其他与Web相关的东西.
* **Do** create an **HTTP API Client** package, named as *CompanyName.ModuleName.HttpApi.Client*, to provide client services for the HTTP API package. Those client services implement application interfaces as clients to a remote endpoint. * **推荐** 创建一个为HTTP API包提供客户端服务的**HTTP API Client**包, 它的命名为*companyname.modulename.httpapi*. 这些客户端服务将应用服务接口实现远程端点的客户端.
* HTTP API Client package only depends on the **Application.Contracts** package. * HTTP API Client package 仅依赖于 **Application.Contracts** package.
* **Do** use dynamic HTTP C# client proxy feature of the ABP framework. * **推荐** 使用ABP框架提供的动态代理HTTP C#客户端的功能.
#### Web Layer #### Web 层
* Do create a **Web** package, named as *CompanyName.ModuleName.Web*, that contains pages, views, scripts, styles, images and other UI components. * **推荐** 创建命名为*CompanyName.ModuleName.Web*的 **Web**包. 包含页面,视图,脚本,样式,图像和其他UI组件.
* Web package only depends on the **HttpApi** package. * Web package 仅依赖于 **HttpApi** package.

69
docs/zh-Hans/Best-Practices/MongoDB-Integration.md

@ -1,12 +1,12 @@
## MongoDB Integration ## MongoDB 集成
* Do define a separated `MongoDbContext` interface and class for each module. * **推荐** 为每个模块定义一个独立的 `MongoDbContext` 接口与实现类.
### MongoDbContext Interface ### MongoDbContext 接口
- **Do** define an **interface** for the `MongoDbContext` that inherits from `IAbpMongoDbContext`. - **推荐** 定义 `MongoDbContext` **接口** 时继承自 `IAbpMongoDbContext`.
- **Do** add a `ConnectionStringName` **attribute** to the `MongoDbContext` interface. - **推荐** 添加 `ConnectionStringName` **attribute** 到 `MongoDbContext` 接口.
- **Do** add `IMongoCollection<TEntity>` **properties** to the `MongoDbContext` interface only for the aggregate roots. Example: - **推荐** 只把聚合根做为 `IMongoCollection<TEntity>` **properties** 添加到 `MongoDbContext` 接口. 示例:
````C# ````C#
[ConnectionStringName("AbpIdentity")] [ConnectionStringName("AbpIdentity")]
@ -17,11 +17,11 @@ public interface IAbpIdentityMongoDbContext : IAbpMongoDbContext
} }
```` ````
### MongoDbContext class ### MongoDbContext 类
- **Do** inherit the `MongoDbContext` from the `AbpMongoDbContext` class. - **推荐** `MongoDbContext` 继承自 `AbpMongoDbContext` 类.
- **Do** add a `ConnectionStringName` attribute to the `MongoDbContext` class. - **推荐** 添加 `ConnectionStringName` **attribute** 到 `MongoDbContext` 类.
- **Do** implement the corresponding `interface` for the `MongoDbContext` class. Example: - **推荐** `MongoDbContext` 类实现相对应的**接口**. 示例:
```c# ```c#
[ConnectionStringName("AbpIdentity")] [ConnectionStringName("AbpIdentity")]
@ -34,21 +34,21 @@ public class AbpIdentityMongoDbContext : AbpMongoDbContext, IAbpIdentityMongoDbC
} }
``` ```
### Collection Prefix ### Collection 前缀
- **Do** add static `CollectionPrefix` **property** to the `DbContext` class. Set default value from a constant. Example: - **推荐** 添加静态 `CollectionPrefix` **property** 到 `DbContext` 类中并使用常量为其设置默认值. 示例:
```c# ```c#
public static string CollectionPrefix { get; set; } = AbpIdentityConsts.DefaultDbTablePrefix; public static string CollectionPrefix { get; set; } = AbpIdentityConsts.DefaultDbTablePrefix;
``` ```
Used the same constant defined for the EF Core integration table prefix in this example. 在此示例中使用与EF Core集成表前缀相同的常量.
- **Do** always use a short `CollectionPrefix` value for a module to create **unique collection names** in a shared database. `Abp` collection prefix is reserved for ABP core modules. - **推荐** 总是使用简短的 `CollectionPrefix` 值为模块在共享数据库中创建 **unique collection names**. `Abp` collection前缀是为ABP Core模块保留的.
### Collection Mapping ### Collection 映射
- **Do** explicitly **configure all aggregate roots** by overriding the `CreateModel` method of the `MongoDbContext`. Example: - **推荐** 通过重写 `MongoDbContext` 的 `CreateModel` 方法 **配置所有的聚合根** . 示例:
```c# ```c#
protected override void CreateModel(IMongoModelBuilder modelBuilder) protected override void CreateModel(IMongoModelBuilder modelBuilder)
@ -62,7 +62,7 @@ protected override void CreateModel(IMongoModelBuilder modelBuilder)
} }
``` ```
- **Do not** configure model directly in the `CreateModel` method. Instead, create an **extension method** for the `IMongoModelBuilder`. Use Configure*ModuleName* as the method name. Example: - **不推荐** 直接在 `CreateModel` 方法中配置model,而是为 `IMongoModelBuilder` 定义一个 **扩展方法**. 使用Configure*ModuleName*作为方法名称. 示例:
```c# ```c#
public static class AbpIdentityMongoDbContextExtensions public static class AbpIdentityMongoDbContextExtensions
@ -90,7 +90,7 @@ public static class AbpIdentityMongoDbContextExtensions
} }
``` ```
- **Do** create a **configuration options** class by inheriting from the `MongoModelBuilderConfigurationOptions`. Example: - **推荐** 通过继承 `MongoModelBuilderConfigurationOptions` 来创建 **configuration Options** 类. 示例:
```c# ```c#
public class IdentityMongoModelBuilderConfigurationOptions public class IdentityMongoModelBuilderConfigurationOptions
@ -103,7 +103,7 @@ public class IdentityMongoModelBuilderConfigurationOptions
} }
``` ```
* **Do** explicitly configure `BsonClassMap` for all entities. Create a static method for this purpose. Example: * **推荐** 创建一个静态方法, 显示地为所有的实体配置 `BsonClassMap`. 示例:
````C# ````C#
public static class AbpIdentityBsonClassMap public static class AbpIdentityBsonClassMap
@ -129,11 +129,11 @@ public static class AbpIdentityBsonClassMap
} }
```` ````
`BsonClassMap` works with static methods. So, it is only needed to configure entities once in an application. `OneTimeRunner` guarantees that it runs in a thread safe manner and only once in the application life. Such a mapping above ensures that unit test properly run. This code will be called by the **module class** below. `BsonClassMap` 适用于静态方法. 所以只需要在应用程序配置一次实体. `OneTimeRunner` 以线程安全的方式运行, 并且在应用程序生命周期中只运行一次. 上面代码中的映射确保单元测试可以正确运行. 此代码将由下面的**模块类**调用.
### Repository Implementation ### 仓储实现
- **Do** **inherit** the repository from the `MongoDbRepository<TMongoDbContext, TEntity, TKey>` class and implement the corresponding repository interface. Example: - **推荐** 仓储 **继承自** `MongoDbRepository<TMongoDbContext, TEntity, TKey>` 类并且实现其相应的接口. 示例:
```c# ```c#
public class MongoIdentityUserRepository public class MongoIdentityUserRepository
@ -148,7 +148,7 @@ public class MongoIdentityUserRepository
} }
``` ```
- **Do** pass the `cancellationToken` to the MongoDB Driver using the `GetCancellationToken` helper method. Example: - **推荐** 使用 `GetCancellationToken` 帮助方法将 `cancellationToken` 传递给MongoDB驱动程序. 示例:
```c# ```c#
public async Task<IdentityUser> FindByNormalizedUserNameAsync( public async Task<IdentityUser> FindByNormalizedUserNameAsync(
@ -164,19 +164,20 @@ public async Task<IdentityUser> FindByNormalizedUserNameAsync(
} }
``` ```
`GetCancellationToken` fallbacks to the `ICancellationTokenProvider.Token` to obtain the cancellation token if it is not provided by the caller code. 如果调用者代码中未提供取消令牌, 则 `GetCancellationToken` 会从`ICancellationTokenProvider.Token` 获取取消令牌
`GetCancellationToken`.
* **Do** ignore the `includeDetails` parameters for the repository implementation since MongoDB loads the aggregate root as a whole (including sub collections) by default. * **推荐** 忽略仓储实现中的 `includeDetails` 参数, 因为MongoDB在默认情况下将聚合根作为一个整体(包括子集合)加载.
* **Do** use the `GetMongoQueryable()` method to obtain an `IQueryable<TEntity>` to perform queries wherever possible. Because; * **推荐** 使用 `GetMongoQueryable()` 方法获取 `IQueryable<TEntity>` 以尽可能执行查询use the `GetMongoQueryable()` method to obtain an `IQueryable<TEntity>` to perform queries wherever possible. 因为;
* `GetMongoQueryable()` method automatically uses the `ApplyDataFilters` method to filter the data based on the current data filters (like soft delete and multi-tenancy). * `GetMongoQueryable()` 方法在内部使用 `ApplyDataFilters` 方法根据当前的过滤器 (如 软删除与多租户)过滤数据.
* Using `IQueryable<TEntity>` makes the code as much as similar to the EF Core repository implementation and easy to write and read. * 使用`IQueryable<TEntity>`让代码与EF Core仓储实现类似, 易于使用.
* **Do** implement data filtering if it is not possible to use the `GetMongoQueryable()` method. * **推荐** 如果无法使用 `GetMongoQueryable()` 方法, 则应自行实现数据过滤.
### Module Class ### 模块类
- **Do** define a module class for the MongoDB integration package. - **推荐** 为MongoDB集成包定义一个模块类.
- **Do** add `MongoDbContext` to the `IServiceCollection` using the `AddMongoDbContext<TMongoDbContext>` method. - **推荐** 使用 `AddMongoDbContext<TMongoDbContext>` 方法将 `MongoDbContext` 添加到 `IServiceCollection`.
- **Do** add implemented repositories to the options for the `AddMongoDbContext<TMongoDbContext>` method. Example: - **推荐** 将已实现的仓储添加到 `AddMongoDbContext<TMongoDbContext>` 方法options中. 示例:
```c# ```c#
[DependsOn( [DependsOn(
@ -198,4 +199,4 @@ public class AbpIdentityMongoDbModule : AbpModule
} }
``` ```
Notice that this module class also calls the static `BsonClassMap` configuration method defined above. 需要注意的是, 模块类还调用上面定义的静态 `BsonClassMap` 配置方法.

52
docs/zh-Hans/Best-Practices/Repositories.md

@ -1,14 +1,14 @@
## Repository Best Practices & Conventions ## 仓储最佳实践 & 约定
### Repository Interfaces ### 仓储接口
* **Do** define repository interfaces in the **domain layer**. * **推荐** 在**领域层**中定义仓储接口.
* **Do** define a repository interface (like `IIdentityUserRepository`) and create its corresponding implementations for **each aggregate root**. * **推荐** 为**每个聚合根**定义仓储接口(如 `IIdentityUserRepository`)并创建相应的实现.
* **Do** always use the created repository interface from the application code. * **推荐** 在应用代码中使用仓储时应该注入仓储接口.
* **Do not** use generic repository interfaces (like `IRepository<IdentityUser, Guid>`) from the application code. * **不推荐** 在应用代码中使用泛型仓储接口(如 `IRepository<IdentityUser, Guid>`).
* **Do not** use `IQueryable<TEntity>` features in the application code (domain, application... layers). * **不推荐** 在应用代码(领域, 应用... 层)中使用 `IQueryable<TEntity>` 特性.
For the example aggregate root: 聚合根的示例:
````C# ````C#
public class IdentityUser : AggregateRoot<Guid> public class IdentityUser : AggregateRoot<Guid>
@ -17,7 +17,7 @@ public class IdentityUser : AggregateRoot<Guid>
} }
```` ````
Define the repository interface as below: 定义仓储接口, 如下所示:
````C# ````C#
public interface IIdentityUserRepository : IBasicRepository<IdentityUser, Guid> public interface IIdentityUserRepository : IBasicRepository<IdentityUser, Guid>
@ -26,14 +26,14 @@ public interface IIdentityUserRepository : IBasicRepository<IdentityUser, Guid>
} }
```` ````
* **Do not** inherit the repository interface from the `IRepository<TEntity, TKey>` interface. Because it inherits the `IQueryable` and the repository should not expose `IQueryable` to the application. * **不推荐** 仓储接口继承 `IRepository<TEntity, TKey>` 接口. 因为它继承了 `IQueryable` 而仓储不应该将`IQueryable`暴漏给应用.
* **Do** inherit the repository interface from `IBasicRepository<TEntity, TKey>` (as normally) or a lower-featured interface, like `IReadOnlyRepository<TEntity, TKey>` (if it's needed). * **推荐** 通常仓储接口继承自 `IBasicRepository<TEntity, TKey>` 或更低级别的接口, 如 `IReadOnlyRepository<TEntity, TKey>` (在需要的时候).
* **Do not** define repositories for entities those are **not aggregate roots**. * **不推荐** 为实体定义仓储接口,因为它们**不是聚合根**.
### Repository Methods ### 仓储方法
* **Do** define all repository methods as **asynchronous**. * **推荐** 所有的仓储方法定义为 **异步**.
* **Do** add an **optional** `cancellationToken` parameter to every method of the repository. Example: * **推荐** 为仓储的每个方法添加 **可选参数** `cancellationToken` . 例:
````C# ````C#
Task<IdentityUser> FindByNormalizedUserNameAsync( Task<IdentityUser> FindByNormalizedUserNameAsync(
@ -42,7 +42,7 @@ Task<IdentityUser> FindByNormalizedUserNameAsync(
); );
```` ````
* **Do** create a **synchronous extension** method for each asynchronous repository method. Example: * **推荐** 为仓储的每个异步方法创建一个 **同步扩展** 方法. 示例:
````C# ````C#
public static class IdentityUserRepositoryExtensions public static class IdentityUserRepositoryExtensions
@ -58,9 +58,9 @@ public static class IdentityUserRepositoryExtensions
} }
```` ````
This will allow synchronous code to use the repository methods easier. 对于同步方法而言, 这会让它们更方便的调用仓储方法.
* **Do** add an optional `bool includeDetails = true` parameter (default value is `true`) for every repository method which returns a **single entity**. Example: * **推荐** 为仓储中返回**单个实体**的方法添加一个可选参数 `bool includeDetails = true` (默认值为`true`). 示例:
````C# ````C#
Task<IdentityUser> FindByNormalizedUserNameAsync( Task<IdentityUser> FindByNormalizedUserNameAsync(
@ -70,9 +70,9 @@ Task<IdentityUser> FindByNormalizedUserNameAsync(
); );
```` ````
This parameter will be implemented for ORMs to eager load sub collections of the entity. 该参数由ORM实现, 用来加载实体子集合.
* **Do** add an optional `bool includeDetails = false` parameter (default value is `false`) for every repository method which returns a **list of entities**. Example: * **推荐** 为仓储中返回**实体列表**的方法添加一个可选参数 `bool includeDetails = false` (默认值为`false`). 示例:
````C# ````C#
Task<List<IdentityUser>> GetListByNormalizedRoleNameAsync( Task<List<IdentityUser>> GetListByNormalizedRoleNameAsync(
@ -82,11 +82,11 @@ Task<List<IdentityUser>> GetListByNormalizedRoleNameAsync(
); );
```` ````
* **Do not** create composite classes to combine entities to get from repository with a single method call. Examples: *UserWithRoles*, *UserWithTokens*, *UserWithRolesAndTokens*. Instead, properly use `includeDetails` option to add all details of the entity when needed. * **不推荐** 创建复合类通过调用仓储单个方法返回组合实体. 比如: *UserWithRoles*, *UserWithTokens*, *UserWithRolesAndTokens*. 相反, 正确的使用 `includeDetails` 选项, 在需要时加载实体所有的详细信息.
* **Avoid** to create projection classes for entities to get less property of an entity from the repository. Example: Avoid to create BasicUserView class to select a few properties needed for the use case needs. Instead, directly use the aggregate root class. However, there may be some exceptions for this rule, where: * **避免** 为了从仓储中获取实体的部分属性而为实体创建投影类. 比如: 避免通过创建BasicUserView来选择所需的一些属性. 相反可以直接使用聚合根类. 不过这条规则有例外情况:
* Performance is so critical for the use case and getting the whole aggregate root highly impacts the performance. * 性能对于用例来说非常重要,而且使用整个聚合根对性能的影响非常大.
### See Also ### 另外请参阅
* [Entity Framework Core Integration](Entity-Framework-Core-Integration.md) * [Entity Framework Core 集成](Entity-Framework-Core-Integration.md)
* [MongoDB Integration](MongoDB-Integration.md) * [MongoDB 集成](MongoDB-Integration.md)

2
docs/zh-Hans/Entity-Framework-Core.md

@ -1,6 +1,6 @@
## Entity Framework Core 集成 ## Entity Framework Core 集成
本文档介绍了如何将EF Core作为ORM提供程序集成到基于ABP的应用程序以及如何对其进行配置. 本文介绍了如何将EF Core作为ORM提供程序集成到基于ABP的应用程序以及如何对其进行配置.
### 安装 ### 安装

166
docs/zh-Hans/Exception-Handling.md

@ -1,25 +1,25 @@
## Exception Handling ## 异常处理
ABP provides a built-in infrastructure and offers a standard model for handling exceptions in a web application. ABP提供了用于处理Web应用程序异常的标准模型.
* Automatically **handles all exceptions** and sends a standard **formatted error message** to the client for an API/AJAX request. * 自动 **处理所有异常** .如果是API/AJAX请求,会向客户端返回一个**标准格式化后的错误消息** .
* Automatically hides **internal infrastructure errors** and returns a standard error message. * 自动隐藏 **内部详细错误** 并返回标准错误消息.
* Provides a configurable way to **localize** exception messages. * 为异常消息的 **本地化** 提供一种可配置的方式.
* Automatically maps standard exceptions to **HTTP status codes** and provides a configurable option to map these to custom exceptions. * 自动为标准异常设置 **HTTP状态代码** ,并提供可配置选项,以映射自定义异常.
### Automatic Exception Handling ### 自动处理异常
`AbpExceptionFilter` handles an exception if **any of the following conditions** are meet: 当满足下面**任意一个条件**时,`AbpExceptionFilter` 会处理此异常:
* Exception is thrown by a **controller action** which returns an **object result** (not a view result). * 当**controller action**方法返回类型是**object**(不是view)并有异常抛出时.
* The request is an AJAX request (`X-Requested-With` HTTP header value is `XMLHttpRequest`). * 当是一个请求为AJAX(Http请求头中`X-Requested-With`为`XMLHttpRequest`)时.
* Client explicitly accepts the `application/json` content type (via `accept` HTTP header). * 当客户端接受的返回类型为`application/json`(Http请求头中`accept` 为`application/json`)时.
If the exception is handled it's automatically **logged** and a formatted **JSON message** is returned to the client. 如果异常被处理过,则会自动**记录日志**并将格式化的**JSON消息**返回给客户端.
#### Error Message Format #### 异常消息格式
Error Message is an instance of the `RemoteServiceErrorResponse` class. The simplest error JSON has a **message** property as shown below: 每个异常消息都是`RemoteServiceErrorResponse` 类的实例.下面是一个只有 **Message** 属性的错误JSON:
````json ````json
{ {
@ -29,11 +29,11 @@ Error Message is an instance of the `RemoteServiceErrorResponse` class. The simp
} }
```` ````
There are **optional fields** those can be filled based upon the exception that has occured. 当异常发生时,会自动填充到这些**可选字段**.
##### Error Code ##### 错误代码
Error **code** is an optional and unique string value for the exception. Thrown `Exception` should implement the `IHasErrorCode` interface to fill this field. Example JSON value: 错误的 **Code** 是字符串类型,并要求唯一的可选属性.如果抛出的异常包含 **Code** 属性,那么应该实现`IHasErrorCode` 接口,来填充这个字段.示例JSON如下:
````json ````json
{ {
@ -44,11 +44,11 @@ Error **code** is an optional and unique string value for the exception. Thrown
} }
```` ````
Error code can also be used to localize the exception and customize the HTTP status code (see the related sections below). 错误 **Code** 同样可用于异常的本地化及自定义HTTP状态代码(请参阅下面的相关部分).
##### Error Details ##### 错误详细信息
Error **details** in an optional field of the JSON error message. Thrown `Exception` should implement the `IHasErrorDetails` interface to fill this field. Example JSON value: 错误的 **Details** 是可选属性.抛出的异常应该实现`IHasErrorDetails` 接口来填充这个字段.示例JSON如下:
```json ```json
{ {
@ -60,9 +60,9 @@ Error **details** in an optional field of the JSON error message. Thrown `Except
} }
``` ```
##### Validation Errors ##### 验证错误
**validationErrors** is a standard field that is filled if the thrown exception implements the `IHasValidationErrors` interface. 当抛出的异常继承至`IHasValidationErrors` 接口时,返回错误对象会包含一个可选属性**validationErrors** .示例JSON如下:
````json ````json
{ {
@ -81,15 +81,15 @@ Error **details** in an optional field of the JSON error message. Thrown `Except
} }
```` ````
`AbpValidationException` implements the `IHasValidationErrors` interface and it is automatically thrown by the framework when a request input is not valid. So, usually you don't need to deal with validation errors unless you have higly customised validation logic. `AbpValidationException`已经实现了`IHasValidationErrors`接口,当请求输入无效时,框架会自动抛出此错误. 因此,除非您有自定义的验证逻辑,否则不需要处理验证错误.
#### Logging #### 日志
Caught exceptions are automatically logged. 自动记录捕获异常的日志.
##### Log Level ##### 日志级别
Exceptions are logged with the `Error` level by default. The Log level can be determined by the exception if it implements the `IHasLogLevel` interface. Example: 默认情况下,记录异常级别为`Error` .可以通过实现`IHasLogLevel` 接口来指定日志的级别,例如:
````C# ````C#
public class MyException : Exception, IHasLogLevel public class MyException : Exception, IHasLogLevel
@ -100,9 +100,9 @@ public class MyException : Exception, IHasLogLevel
} }
```` ````
##### Self Logging Exceptions ##### 异常自定义日志
Some exception types may need to write additional logs. They can implement the `IExceptionWithSelfLogging` if needed. Example: 某些异常类型可能需要记录额外日志信息.可以通过实现`IExceptionWithSelfLogging` 来记录指定日志,例如:
````C# ````C#
public class MyException : Exception, IExceptionWithSelfLogging public class MyException : Exception, IExceptionWithSelfLogging
@ -114,46 +114,46 @@ public class MyException : Exception, IExceptionWithSelfLogging
} }
```` ````
> `ILogger.LogException` extension methods is used to write exception logs. You can use the same extension method when needed. > 扩展方法`ILogger.LogException` 用来记录日志. 在需要时可以使用相同的扩展方法.
### Business Exceptions ### 业务异常
Most of your own exceptions will be business exceptions. The `IBusinessException` interface is used to mark an exception as a business exception. 大多数异常都是业务异常.可以通过使用`IBusinessException` 接口来标记异常为业务异常.
`BusinessException` implements the `IBusinessException` interface in addition to the `IHasErrorCode`, `IHasErrorDetails` and `IHasLogLevel` interfaces. The default log level is `Warning`. `BusinessException` 除了实现`IHasErrorCode`,`IHasErrorDetails` ,`IHasLogLevel` 接口外,还实现了`IBusinessException` 接口.其默认日志级别为`Warning`.
Usually you have an error code related to a particular business exception. For example: 通常你会将一个错误代码关联至特定的业务异常.例如:
````C# ````C#
throw new BusinessException(QaErrorCodes.CanNotVoteYourOwnAnswer); throw new BusinessException(QaErrorCodes.CanNotVoteYourOwnAnswer);
```` ````
`QaErrorCodes.CanNotVoteYourOwnAnswer` is just a `const string`. The following error code format is recommended: `QaErrorCodes.CanNotVoteYourOwnAnswer` 是一个字符串常量. 建议使用下面的错误代码格式:
```` ````
<code-namespace>:<error-code> <code-namespace>:<error-code>
```` ````
**code-namespace** is a **unique value** specific to your module/application. Example: **code-namespace**,应在指定的模块/应用层中保证其唯一.例如:
```` ````
Volo.Qa:010002 Volo.Qa:010002
```` ````
`Volo.Qa` is the code-namespace here. code-namespace is then will be used while **localizing** exception messages. `Volo.Qa`在这是作为`code-namespace`. `code-namespace` 同样可以在异常 **本地化** 中使用.
* You can **directly throw** a `BusinessException` or **derive** your own exception types from it when needed. * 你可以直接抛出一个 `BusinessException` 异常或者自定义的异常.
* All properties are optional for the `BusinessException` class. But you generally set either `ErrorCode` or `Message` property. * 对于`BusinessException` 类型,其所有属性都是可选的.但是通常会设置`ErrorCode`或`Message`属性.
### Exception Localization ### 异常本地化
One problem with throwing exceptions is how to localize error messages while sending it to the client. ABP offers two models and their variants. 这里有个问题,就是如何在发送错误消息到客户端时,对错误消息进行本地化.ABP提供了2个模型.
#### User Friendly Exception #### 用户友好异常
If an exception implements the `IUserFriendlyException` interface, then ABP does not change it's `Message` and `Details` properties and directly send it to the client. 如果异常实现了 `IUserFriendlyException` 接口,那么ABP不会修改 `Message`和`Details`属性,而直接将它发送给客户端.
`UserFriendlyException` class is the built-in implementation of the `IUserFriendlyException` interface. Example usage: `UserFriendlyException` 类默认实现了 `IUserFriendlyException` 接口,示例如下:
````C# ````C#
throw new UserFriendlyException( throw new UserFriendlyException(
@ -161,13 +161,13 @@ throw new UserFriendlyException(
); );
```` ````
In this way, there is **no need for localization** at all. If you want to localize the message, you can inject and use the standard **string localizer** (see the [localization document](Localization.md)). Example: 采用这种方式是不需要本地化的.如果需要本地化消息,则可以注入**string localizer**( 请参阅[本地化文档](Localization.md) )来实现. 例:
````C# ````C#
throw new UserFriendlyException(_stringLocalizer["UserNameShouldBeUniqueMessage"]); throw new UserFriendlyException(_stringLocalizer["UserNameShouldBeUniqueMessage"]);
```` ````
Then define it in the **localization resource** for each language. Example: 再在本地化资源的语言中添加对应的定义.例如:
````json ````json
{ {
@ -178,30 +178,30 @@ Then define it in the **localization resource** for each language. Example:
} }
```` ````
String localizer already supports **parameterized messages**. For example: **string localizer** 是支持格式化参数.例如
````C# ````C#
throw new UserFriendlyException(_stringLocalizer["UserNameShouldBeUniqueMessage", "john"]); throw new UserFriendlyException(_stringLocalizer["UserNameShouldBeUniqueMessage", "john"]);
```` ````
Then the localization text can be: 其本地化文本如下:
````json ````json
"UserNameShouldBeUniqueMessage": "Username should be unique! '{0}' is already taken!" "UserNameShouldBeUniqueMessage": "Username should be unique! '{0}' is already taken!"
```` ````
* The `IUserFriendlyException` interface is derived from the `IBusinessException` and the `UserFriendlyException` class is derived from the `BusinessException` class. * `IUserFriendlyException`接口派生自`IBusinessException`,而 `UserFriendlyException `类派生自`BusinessException`类.
#### Using Error Codes #### 错误代码
`UserFriendlyException` is fine, but it has a few problems in advanced usages: `UserFriendlyException`很好用,但是在一些高级用法里面,它存在以下问题:
* It requires you to **inject the string localizer** everywhere and always use it while throwing exceptions. * 在抛出异常的地方必须注入**string localizer** 来实现本地化 .
* However, in some of the cases, it may **not be possible** to inject the string localizer (in a static context or in an entity method). * 但是,在某些情况下,**可能注入不了string localizer**(比如,静态方法或实体中)
Instead of localizing the message while throwing the exception, you can separate the process using **error codes**. 那么这时就可以通过使用 **错误代码** 的方式来处理本地化,而不是在抛出异常的时候.
First, define the **code-namespace** to **localization resource** mapping in the module configuration: 首先,在模块配置代码中将 **code-namespace** 映射至 **本地化资源**:
````C# ````C#
services.Configure<ExceptionLocalizationOptions>(options => services.Configure<ExceptionLocalizationOptions>(options =>
@ -210,7 +210,7 @@ services.Configure<ExceptionLocalizationOptions>(options =>
}); });
```` ````
Then any of the exceptions with `Volo.Qa` namespace will be localized using their given localization resource. The localization resource should always have an entry with the error code key. Example: 再使用本地化资源,来本地化`Volo.Qa`命名空间下的所有异常. 本地化资源中应包含对应错误代码的文本. 例如:
````json ````json
{ {
@ -221,18 +221,18 @@ Then any of the exceptions with `Volo.Qa` namespace will be localized using thei
} }
```` ````
Then a business exception can be thrown with the error code: 最后就可以抛出一个包含错误代码的业务异常了:
````C# ````C#
throw new BusinessException(QaDomainErrorCodes.CanNotVoteYourOwnAnswer); throw new BusinessException(QaDomainErrorCodes.CanNotVoteYourOwnAnswer);
```` ````
* Throwing any exception implementing the `IHasErrorCode` interface behaves the same. So, the error code localization approach is not unique to the `BusinessException` class. * 所有实现`IHasErrorCode` 接口的异常都具有相同的行为.因此,对错误代码的本地化,并不是`BusinessException`类所特有的.
* Defining localized string is not required for an error message. If it's not defined, ABP sends the default error message to the client. It does not use the `Message` property of the exception! if you want that, use the `UserFriendlyException` (or use an exception type that implements the `IUserFriendlyException` interface). * 错误消息的本地化文本的并不是必须. 如果未定义,ABP会将默认的错误消息发送给客户端. 而并不是发送异常的`Message`属性. 如果你想要发送异常的`Message`,使用`UserFriendlyException`(或使用实现`IUserFriendlyException`接口的异常类型)
##### Using Message Parameters ##### 使用消息的格式化参数
If you have a parameterized error message, then you can set it with the exception's `Data` property. For example: 如果错误消息包含格式化参数时,则可以使用异常的`Data`属性进行设置.例如:
````C# ````C#
throw new BusinessException("App:010046") throw new BusinessException("App:010046")
@ -245,14 +245,14 @@ throw new BusinessException("App:010046")
```` ````
Fortunately there is a shortcut way to code this: 另外有一种更为快捷的方式:
````C# ````C#
throw new BusinessException("App:010046") throw new BusinessException("App:010046")
.WithData("UserName", "john"); .WithData("UserName", "john");
```` ````
Then the localized text can contain the `UserName` parameter: 下面就是一个包含`UserName` 参数的错误消息:
````json ````json
{ {
@ -263,26 +263,26 @@ Then the localized text can contain the `UserName` parameter:
} }
```` ````
* `WithData` can be chained with more than one parameter (like `.WithData(...).WithData(...)`). * `WithData` 支持链式调用 (如`.WithData(...).WithData(...)`).
### HTTP Status Code Mapping ### HTTP状态代码 映射
ABP tries to automatically determine the most suitable HTTP status code for common exception types by following these rules: ABP尝试按照以下规则,自动映射常见的异常类型的HTTP状态代码:
* For the `AbpAuthorizationException`: * 对于 `AbpAuthorizationException`:
* Returns `401` (unauthorized) if user has not logged in. * 用户没有登录,返回 `401` (未认证).
* Returns `403` (forbidden) if user has logged in. * 用户已登录,但是当前访问未授权,返回 `403` (未授权).
* Returns `400` (bad request) for the `AbpValidationException`. * 对于 `AbpValidationException` 返回 `400` (错误的请求) .
* Returns `404` (not found) for the `EntityNotFoundException`. * 对于 `EntityNotFoundException`返回 `404` (未找到).
* Returns `403` (forbidden) for the `IBusinessException` (and `IUserFriendlyException` since it extends the `IBusinessException`). * 对于 `IBusinessException` 和 `IUserFriendlyException` (它是`IBusinessException`的扩展) 返回`403` (未授权) .
* Returns `501` (not implemented) for the `NotImplementedException`. * 对于 `NotImplementedException` 返回 `501` (未实现) .
* Returns `500` (internal server error) for other exceptions (those are assumed as infrastructure exceptions). * 对于其他异常 (基础架构中未定义的) 返回 `500` (服务器内部错误) .
The `IHttpExceptionStatusCodeFinder` is used to automatically determine the HTTP status code. The default implementation is the `DefaultHttpExceptionStatusCodeFinder` class. It can be replaced or extended as needed. `IHttpExceptionStatusCodeFinder` 是用来自动判断HTTP状态代码.默认的实现是`DefaultHttpExceptionStatusCodeFinder`.可以根据需要对其进行更换或扩展.
#### Custom Mappings #### 自定义映射
Automatic HTTP status code determination can be overrided by custom mappings. For example: 可以重写HTTP状态代码的自动映射,示例如下:
````C# ````C#
services.Configure<ExceptionHttpStatusCodeOptions>(options => services.Configure<ExceptionHttpStatusCodeOptions>(options =>
@ -291,12 +291,12 @@ services.Configure<ExceptionHttpStatusCodeOptions>(options =>
}); });
```` ````
### Built-In Exceptions ### 内置的异常
Some exception types are automatically thrown by the framework: 框架会自动抛出以下异常类型:
- `AbpAuthorizationException` is thrown if the current user has no permission to perform the requested operation. See authorization document (TODO: link) for more. - 当用户没有权限执行操作时,会抛出 `AbpAuthorizationException` 异常. 有关更多信息,请参阅授权文档(TODO:link).
- `AbpValidationException` is thrown if the input of the current request is not valid. See validation document (TODO: link) for more. - 如果当前请求的输入无效,则抛出`AbpValidationException 异常`. 有关更多信息,请参阅授权文档(TODO:link).
- `EntityNotFoundException` is thrown if the requested entity is not available. This is mostly thrown by [repositories](Repositories.md). - 如果请求的实体不存在,则抛出`EntityNotFoundException` 异常. 此异常由 [repositories](Repositories.md) 抛出.
You can also throw these type of exceptions in your code (although it's rarely needed). 你同样可以在代码中抛出这些类型的异常(虽然只在很少时候)

2
docs/zh-Hans/Index.md

@ -57,7 +57,7 @@
* [客户端包管理](AspNetCore/Client-Side-Package-Management.md) * [客户端包管理](AspNetCore/Client-Side-Package-Management.md)
* [捆绑&压缩](AspNetCore/Bundling-Minification.md) * [捆绑&压缩](AspNetCore/Bundling-Minification.md)
* [Tag Helpers](Tag-Helpers.md) * [Tag Helpers](Tag-Helpers.md)
* [主题化](AspNetCore/Theming.md) * [主题](AspNetCore/Theming.md)
* 数据访问 * 数据访问
* [Entity Framework Core 集成](Entity-Framework-Core.md) * [Entity Framework Core 集成](Entity-Framework-Core.md)
* [MongoDB 集成](MongoDB.md) * [MongoDB 集成](MongoDB.md)

131
docs/zh-Hans/Multi-Tenancy.md

@ -1,22 +1,22 @@
## Multi-Tenancy ## 多租户
ABP Multi-tenancy module provides base functionality to create multi tenant applications. ABP的多租户模块提供了创建多租户应用程序的基本功能.
Wikipedia [defines](https://en.wikipedia.org/wiki/Multitenancy) multi-tenancy as like that: 维基百科中是这样[定义](https://en.wikipedia.org/wiki/Multitenancy)多租户的:
> Software **Multi-tenancy** refers to a software **architecture** in which a **single instance** of a software runs on a server and serves **multiple tenants**. A tenant is a group of users who share a common access with specific privileges to the software instance. With a multitenant architecture, a software application is designed to provide every tenant a **dedicated share of the instance including its data**, configuration, user management, tenant individual functionality and non-functional properties. Multi-tenancy contrasts with multi-instance architectures, where separate software instances operate on behalf of different tenants. > 软件多租户技术指的是一种软件架构,这种架构可以使用软件的单实例运行并为多个租户提供服务.租户是通过软件实例的特定权限共享通用访问的一组用户.使用多租户架构,软件应用为每个租户提供实例的专用共享,包括实例的数据、配置、用户管理、租户的私有功能和非功能属性.多租户与多实例架构形成对比,将软件实例的行为根据不同的租户分割开来.
### Volo.Abp.MultiTenancy.Abstractions Package ### Volo.Abp.MultiTenancy.Abstractions
Volo.Abp.MultiTenancy.Abstractions package defines fundamental interfaces to make your code "multi-tenancy ready". So, install it to your project using the package manager console (PMC): Volo.Abp.MultiTenancy.Abstractions定义了一些基础接口让你的代码"multi-tenancy ready",使用包管理器控制台(PMC)将它安装到你的项目中:
```` ````
Install-Package Volo.Abp.MultiTenancy.Abstractions Install-Package Volo.Abp.MultiTenancy.Abstractions
```` ````
> This package is already installed by default with the startup template. So, most of the time, you don't need to install it manually. > 这个包默认安装在了快速启动模板中.所以,大多数情况下,你不需要手动安装它.
Then you can add **AbpMultiTenancyAbstractionsModule** dependency to your module: 然后你可以添加 **AbpMultiTenancyAbstractionsModule** 依赖到你的模块:
````C# ````C#
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
@ -32,11 +32,11 @@ namespace MyCompany.MyProject
} }
```` ````
> With the "Multi-tenancy ready" concept, we intent to develop our code to be compatible with multi-tenancy approach. Then it can be used in a multi-tenant application or not, depending on the requirements of the final application. > 随着"Multi-tenancy ready"的概念,我们打算开发我们的代码和多租户方法兼容.然后它可以被用于多租户和非多租户的程序中,这取决于最终程序的需求.
#### Define Entities #### 定义实体
You can implement **IMultiTenant** interface for your entities to make them multi-tenancy ready. Example: 你可以在你的实体中实现 **IMultiTenant** 接口来实现多租户,例如:
````C# ````C#
using System; using System;
@ -47,7 +47,7 @@ namespace MyCompany.MyProject
{ {
public class Product : AggregateRoot, IMultiTenant public class Product : AggregateRoot, IMultiTenant
{ {
public Guid? TenantId { get; set; } //IMultiTenant defines TenantId property public Guid? TenantId { get; set; } //IMultiTenant 定义了 TenantId 属性
public string Name { get; set; } public string Name { get; set; }
@ -56,11 +56,11 @@ namespace MyCompany.MyProject
} }
```` ````
IMultiTenant requires to define a **TenantId** property in the implementing entity (See entity documentation (TODO: link) for more about entities). 实现IMultiTenant接口,需要在实体中定义一个 **TenantId** 的属性(查看更多有关[实体](Entities.md)的文档)
#### Obtain Current Tenant's Id #### 获取当前租户的Id
Your code may require to get current tenant's id (regardless of how it's retrieved actually). You can [inject](Dependency-Injection.md) and use **ICurrentTenant** interface for such cases. Example: 你的代码中可能需要获取当前租户的Id(先不管它具体是怎么取得的).对于这种情况你可以[注入](Dependency-Injection.md)并使用 **ICurrentTenant** 接口.例如:
````C# ````C#
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
@ -80,25 +80,25 @@ namespace MyCompany.MyProject
public void DoIt() public void DoIt()
{ {
var tenantId = _currentTenant.Id; var tenantId = _currentTenant.Id;
//use tenantId in your code... //在你的代码中使用tenantId
} }
} }
} }
```` ````
#### Change Current Tenant #### 改变当前租户
TODO: ... TODO: ...
### Volo.Abp.MultiTenancy Package ### Volo.Abp.MultiTenancy
Volo.Abp.MultiTenancy is the actual package that makes your application multi-tenant. Install it into your project using PMC: Volo.Abp.MultiTenancy 才是让你的程序实现多租户的真正的包.使用PMC将它安装到你的项目中:
```` ````
Install-Package Volo.Abp.MultiTenancy Install-Package Volo.Abp.MultiTenancy
```` ````
Then you can add **AbpMultiTenancyAbstractionsModule** dependency to your module: 然后添加 **AbpMultiTenancyAbstractionsModule** 依赖到你的模块中:
````C# ````C#
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
@ -114,17 +114,18 @@ namespace MyCompany.MyProject
} }
```` ````
> If you add AbpMultiTenancyModule dependency to your module, then you don't need to add AbpMultiTenancyAbstractionsModule dependency separately since AbpMultiTenancyModule already depends on it. > 如果你添加了AbpMultiTenancyModule依赖,就不需要再另外添加AbpMultiTenancyAbstractionsModule依赖了,因为AbpMultiTenancyModule已经依赖它了.
#### Determining Current Tenant #### 确定当前租户
The first thing for a multi-tenant application is to determine the current tenant on the runtime. Volo.Abp.MultiTenancy package only provides abstractions (named as tenant resolver) for determining the current tenant, however it does not have any implementation out of the box. 多租户的应用程序运行的时候首先要做的就是确定当前租户.
Volo.Abp.MultiTenancy只提供了用于确定当前租户的抽象(称为租户解析器),但是并没有现成的实现.
**Volo.Abp.AspNetCore.MultiTenancy** package has implementation to determine the current tenant from current web request (from subdomain, header, cookie, route... etc.). See Volo.Abp.AspNetCore.MultiTenancy Package section later in this document. **Volo.Abp.AspNetCore.MultiTenancy**已经实现了从当前Web请求(从子域名,请求头,cookie,路由...等)中确定当前租户.本文后面会介绍Volo.Abp.AspNetCore.MultiTenancy.
##### Custom Tenant Resolvers ##### 自定义租户解析器
You can add your custom tenant resolver to **TenantResolveOptions** in your module's ConfigureServices method as like below: 你可以像下面这样,在你模块的ConfigureServices方法中将自定义解析器并添加到 **TenantResolveOptions**中:
````C# ````C#
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -149,7 +150,7 @@ namespace MyCompany.MyProject
} }
```` ````
MyCustomTenantResolver must implement **ITenantResolver** as shown below: MyCustomTenantResolver必须像下面这样实现**ITenantResolver**接口:
````C# ````C#
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
@ -160,25 +161,24 @@ namespace MyCompany.MyProject
{ {
public void Resolve(ITenantResolveContext context) public void Resolve(ITenantResolveContext context)
{ {
context.TenantIdOrName = ... //find tenant id or tenant name from somewhere... context.TenantIdOrName = ... //从其他地方获取租户id或租户名字...
} }
} }
} }
```` ````
A tenant resolver can set **TenantIdOrName** if it can determine it. If not, just leave it as is to allow next resolver to determine it. 如果能确定租户id或租户名字可以在租户解析器中设置 **TenantIdOrName**.如果不能确定,那就空着让下一个解析器来确定它.
#### Tenant Store #### 租户存储
Volo.Abp.MultiTenancy package defines **ITenantStore** to abstract data source from the framework. You can implement ITenantStore to work with any data source (like a relational database) that stores information of your tenants. Volo.Abp.MultiTenancy中定义了 **ITenantStore** 从框架中抽象数据源.你可以实现ITenantStore,让它跟任何存储你租户的数据源(例如关系型数据库)一起工作.
...
##### Configuration Data Store ##### 配置数据存储
There is a built in (and default) tenant store, named ConfigurationTenantStore, that can be used to store tenants using standard [configuration system](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/) (with [Microsoft.Extensions.Configuration](https://www.nuget.org/packages/Microsoft.Extensions.Configuration) package). Thus, you can define tenants as hard coded or get from your appsettings.json file. 有一个内置的(默认的)租户存储,叫ConfigurationTenantStore.它可以被用于存储租户,通过标准的[配置系统](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/)(使用[Microsoft.Extensions.Configuration](https://www.nuget.org/packages/Microsoft.Extensions.Configuration)).因此,你可以通过硬编码或者在appsettings.json文件中定义租户.
###### Example: Define tenants as hard-coded ###### 例子:硬编码定义租户
````C# ````C#
using System; using System;
@ -207,7 +207,7 @@ namespace MyCompany.MyProject
"tenant2" //Name "tenant2" //Name
) )
{ {
//tenant2 has a seperated database //tenant2 有单独的数据库连接字符串
ConnectionStrings = ConnectionStrings =
{ {
{ConnectionStrings.DefaultConnectionStringName, "..."} {ConnectionStrings.DefaultConnectionStringName, "..."}
@ -220,9 +220,9 @@ namespace MyCompany.MyProject
} }
```` ````
###### Example: Define tenants in appsettings.json ###### 例子:appsettings.json定义租户
First create your configuration from your appsettings.json file as you always do. 首先从appsetting.json文件中创建你的配置.
````C# ````C#
using System.IO; using System.IO;
@ -254,7 +254,7 @@ namespace MyCompany.MyProject
} }
```` ````
Then add a "**Tenants**" section to your appsettings.json: 然后在appsettings.json中添加 "**Tenants**" 节点:
````json ````json
"Tenants": [ "Tenants": [
@ -276,29 +276,30 @@ Then add a "**Tenants**" section to your appsettings.json:
TODO: This package implements ITenantStore using a real database... TODO: This package implements ITenantStore using a real database...
#### Tenant Information #### 租户信息
ITenantStore works with **TenantInformation** class that has several properties for a tenant: ITenantStore跟 **TenantInformation**类一起工作,并且包含了几个租户属性:
* **Id**: Unique Id of the tenant. * **Id**:租户的唯一Id.
* **Name**: Unique name of the tenant. * **Name**: 租户的唯一名称.
* **ConnectionStrings**: If this tenant has dedicated database(s) to store it's data, then connection strings can provide database connection strings (it may have a default connection string and connection strings per modules - TODO: Add link to Abp.Data package document). * **ConnectionStrings**:如果这个租户有专门的数据库来存储数据.它可以提供数据库的字符串(它可以具有默认的连接字符串和每个模块的连接字符串).
A multi-tenant application may require additional tenant properties, but these are the minimal requirements for the framework to work with multiple tenants.
#### Change Tenant By Code 多租户应用程序可能需要其他租户属性,但这些属性是框架与多个租户一起使用的最低要求.
#### 代码中改变租户
TODO... TODO...
### Volo.Abp.AspNetCore.MultiTenancy Package ### Volo.Abp.AspNetCore.MultiTenancy
Volo.Abp.AspNetCore.MultiTenancy package integrate multi-tenancy to ASP.NET Core applications. To install it to your project, run the following command on PMC: Volo.Abp.AspNetCore.MultiTenancy将多租户整合到了ASP.NET Core的程序中.在PMC中使用下面的代码将它安装到项目中.
```` ````
Install-Package Volo.Abp.AspNetCore.MultiTenancy Install-Package Volo.Abp.AspNetCore.MultiTenancy
```` ````
Then you can add **AbpAspNetCoreMultiTenancyModule** dependency to your module: 然后添加 **AbpAspNetCoreMultiTenancyModule** 依赖到你的模块:
````C# ````C#
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
@ -314,9 +315,9 @@ namespace MyCompany.MyProject
} }
```` ````
#### Multi-Tenancy Middleware #### 多租户中间件
Volo.Abp.AspNetCore.MultiTenancy package includes the multi-tenancy middleware... Volo.Abp.AspNetCore.MultiTenancy包含了多租户中间件...
````C# ````C#
app.UseMultiTenancy(); app.UseMultiTenancy();
@ -324,16 +325,16 @@ app.UseMultiTenancy();
TODO:... TODO:...
#### Determining Current Tenant From Web Request #### 从Web请求中确定当前租户
Volo.Abp.AspNetCore.MultiTenancy package adds following tenant resolvers to determine current tenant from current web request (ordered by priority). These resolvers are added and work out of the box: Volo.Abp.AspNetCore.MultiTenancy 添加了下面这些租户解析器,从当前Web请求(按优先级排序)中确定当前租户.
* **QueryStringTenantResolver**: Tries to find current tenant id from query string parameter. Parameter name is "__tenant" by default. * **QueryStringTenantResolver**: 尝试从query string参数中获取当前租户,默认参数名为"__tenant".
* **RouteTenantResolver**: Tries to find current tenant id from route (URL path). Variable name is "__tenant" by default. So, if you defined a route with this variable, then it can determine the current tenant from the route. * **RouteTenantResolver**:尝试从当前路由中获取(URL路径),默认是变量名是"__tenant".所以,如果你的路由中定义了这个变量,就可以从路由中确定当前租户.
* **HeaderTenantResolver**: Tries to find current tenant id from HTTP header. Header name is "__tenant" by default. * **HeaderTenantResolver**: 尝试从HTTP header中获取当前租户,默认的header名称是"__tenant".
* **CookieTenantResolver**: Tries to find current tenant id from cookie values. Cookie name is "__tenant" by default. * **CookieTenantResolver**: 尝试从当前cookie中获取当前租户.默认的Cookie名称是"__tenant".
"__tenant" parameter name can be changed using AspNetCoreMultiTenancyOptions. Example: 可以使用AspNetCoreMultiTenancyOptions修改默认的参数名"__tenant".例如:
````C# ````C#
services.Configure<AspNetCoreMultiTenancyOptions>(options => services.Configure<AspNetCoreMultiTenancyOptions>(options =>
@ -342,11 +343,11 @@ services.Configure<AspNetCoreMultiTenancyOptions>(options =>
}); });
```` ````
##### Domain Tenant Resolver ##### 域名租户解析器
In a real application, most of times you will want to determine current tenant either by subdomain (like mytenant1.mydomain.com) or by the whole domain (like mytenant.com). If so, you can configure TenantResolveOptions to add a domain tenant resolver. 实际项目中,大多数情况下你想通过子域名(如mytenant1.mydomain.com)或全域名(如mytenant.com)中确定当前租户.如果是这样,你可以配置TenantResolveOptions添加一个域名租户解析器.
###### Example: Add a subdomain resolver ###### 例子:添加子域名解析器
````C# ````C#
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -363,7 +364,7 @@ namespace MyCompany.MyProject
{ {
context.Services.Configure<TenantResolveOptions>(options => context.Services.Configure<TenantResolveOptions>(options =>
{ {
//Subdomain format: {0}.mydomain.com (adding as the highest priority resolver) //子域名格式: {0}.mydomain.com (作为最高优先级解析器添加)
options.TenantResolvers.Insert(0, new DomainTenantResolver("{0}.mydomain.com")); options.TenantResolvers.Insert(0, new DomainTenantResolver("{0}.mydomain.com"));
}); });
@ -373,15 +374,15 @@ namespace MyCompany.MyProject
} }
```` ````
{0} is the the placeholder to determine current tenant's unique name. {0}是用来确定当前租户唯一名称的占位符.
Instead of ``options.TenantResolvers.Insert(0, new DomainTenantResolver("{0}.mydomain.com"));`` you can use this shortcut: 你可以使用下面的方法,代替``options.TenantResolvers.Insert(0, new DomainTenantResolver("{0}.mydomain.com"));``:
````C# ````C#
options.AddDomainTenantResolver("{0}.mydomain.com"); options.AddDomainTenantResolver("{0}.mydomain.com");
```` ````
###### Example: Add a domain resolver ###### 例子:添加全域名解析器
````C# ````C#
options.AddDomainTenantResolver("{0}.com"); options.AddDomainTenantResolver("{0}.com");

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

@ -1,30 +1,30 @@
## ASP.NET Core MVC Tutorial - Part I ## ASP.NET Core MVC 介绍 - 第一章
### About this Tutorial ### 关于本教程
In this tutorial series, you will build an application that is used to manage a list of books & their authors. **Entity Framework Core** (EF Core) will be used as the ORM provider (as it comes pre-configured with the [startup template](https://abp.io/Templates)). 本教程中,你会创建一个用于管理书籍和书籍作者的程序.会用到 **Entity Framework Core** (EF Core)作为ORM([启动模板](https://abp.io/Templates)中预配置的ORM).
This is the first part of the tutorial series. See all parts: 这是本教程所有章节中的第一章,下面是所有的章节:
- **Part I: Create the project and a book list page (this tutorial)** - **Part I: 创建项目和书籍列表页面(本章)**
- [Part II: Create, Update and Delete books](Part-II.md) - [Part II: 创建,编辑,删除书籍](Part-II.md)
- [Part III: Integration Tests](Part-III.md) - [Part III: 集成测试](Part-III.md)
You can download the **source code** of the application [from here](https://github.com/volosoft/abp/tree/master/samples/BookStore). 你可以从[这里](https://github.com/volosoft/abp/tree/master/samples/BookStore)下载本程序的源码.
### Creating the Project ### 创建项目
Go to the [startup template page](https://abp.io/Templates) and download a new project named `Acme.BookStore`, create the database and run the application by following the [template document](../../Getting-Started-AspNetCore-MVC-Template.md). 打开[启动模板页](https://abp.io/Templates)并下载一个新的项目叫做`Acme.BookStore`.根据[模板文档](../../Getting-Started-AspNetCore-MVC-Template.md)创建数据库并运行这个程序.
### Solution Structure ### 解决方案的结构
This is the how the layered solution structure looks after it's created from the startup template: 下面的图片展示了从启动模板创建的项目是如何分层的.
![bookstore-visual-studio-solution](images/bookstore-visual-studio-solution.png) ![bookstore-visual-studio-solution](images/bookstore-visual-studio-solution.png)
### Create the Book Entity ### 创建Book实体
Define [entities](../../Entities.md) in the **domain layer** (`Acme.BookStore.Domain` project) of the solution. The main entity of the application is the `Book`: 在 **领域层** 定义[实体](../../Entities.md)(`Acme.BookStore.Domain` 中).这个项目最主要的实体就是`Book`:
````C# ````C#
using System; using System;
@ -50,14 +50,14 @@ namespace Acme.BookStore
} }
```` ````
* ABP has two fundamental base classes for entities: `AggregateRoot` and `Entity`. **Aggregate Root** is one of the **Domain Driven Design (DDD)** concepts. See [entity document](../../Entities.md) for more details and best practices. * ABP有两个基本的实体基类: `AggregateRoot` 和 `Entity`.**Aggregate Root**是 **领域驱动设计(DDD)** 中的概念.查看[实体](../../Entities.md)的更多信息和最佳实践.
* `Book` entity inherits `AuditedAggregateRoot` which adds some auditing properties (`CreationTime`, `CreatorId`, `LastModificationTime`... etc.) on top of the `AggregateRoot` class. * `Book`实体继承了`AuditedAggregateRoot`,`AuditedAggregateRoot`类在`AggregateRoot`类的基础上添加了(`CreationTime`, `CreatorId`, `LastModificationTime`... 等.)审计属性.
* `Guid` is the **primary key type** of the `Book` entity. * `Book`实体的主键类型是`Guid`类型.
* Used **data annotation attributes** in this code for EF Core mappings. Alternatively you could use EF Core's [fluent mapping API](https://docs.microsoft.com/en-us/ef/core/modeling) instead. * 使用 **数据注解** 为EF Core添加映射.或者你也可以使用 EF Core 自带的[fluent mapping API](https://docs.microsoft.com/en-us/ef/core/modeling).
#### BookType Enum #### BookType枚举
The `BookType` enum used above is defined as below: 下面是所有要用到的`BookType`枚举:
````C# ````C#
namespace Acme.BookStore namespace Acme.BookStore
@ -77,9 +77,9 @@ namespace Acme.BookStore
} }
```` ````
#### Add Book Entity to Your DbContext #### 将Book实体添加到DbContext中
EF Core requires you to relate entities with your DbContext. The easiest way to do this is to add a `DbSet` property to the `BookStoreDbContext` class in the `Acme.BookStore.EntityFrameworkCore` project, as shown below: EF Core需要你将实体和DbContext建立关联.最简单的做法是在`Acme.BookStore.EntityFrameworkCore`项目的`BookStoreDbContext`类中添加`DbSet`属性.如:
````C# ````C#
public class BookStoreDbContext : AbpDbContext<BookStoreDbContext> public class BookStoreDbContext : AbpDbContext<BookStoreDbContext>
@ -89,31 +89,31 @@ public class BookStoreDbContext : AbpDbContext<BookStoreDbContext>
} }
```` ````
#### Add New Migration & Update the Database #### 添加新的Migration并更新数据库
The Startup template uses [EF Core Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/) to create and maintain the database schema. Open the **Package Manager Console (PMC)** (under the *Tools/Nuget Package Manager* menu), select the `Acme.BookStore.EntityFrameworkCore` as the **default project** and execute the following command: 这个启动模板使用了[EF Core Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/)来创建并维护数据库结构.打开 **Package Manager Console (PMC)** (工具/Nuget包管理器菜单),选择 `Acme.BookStore.EntityFrameworkCore`作为默认的项目然后执行下面的命令:
![bookstore-pmc-add-book-migration](images/bookstore-pmc-add-book-migration.png) ![bookstore-pmc-add-book-migration](images/bookstore-pmc-add-book-migration.png)
This will create a new migration class inside the `Migrations` folder. Then execute the `Update-Database` command to update the database schema: 这样就会在`Migrations`文件夹中创建一个新的migration类.然后执行`Update-Database`命令更新数据库结构.
```` ````
PM> Update-Database PM> Update-Database
```` ````
#### Add Sample Data #### 添加示例数据
`Update-Database` command created the `Books` table in the database. Open your database and enter a few sample rows, so you can show them on the page: `Update-Database`命令会在数据库中创建`Books`表.打开这个表添加几行数据,然后就可以把这些数据展示到页面上:
![bookstore-books-table](images/bookstore-books-table.png) ![bookstore-books-table](images/bookstore-books-table.png)
### Create the Application Service ### 创建应用服务
The next step is to create an [application service](../../Application-Services.md) to manage (create, list, update, delete...) the books. 下一步是创建[应用服务](../../Application-Services.md)来管理(创建,列出,更新,删除...)书籍.
#### BookDto #### BookDto
Create a DTO class named `BookDto` into the `Acme.BookStore.Application` project: 在`Acme.BookStore.Application`项目中添加一个名为`BookDto`的DTO类:
````C# ````C#
using System; using System;
@ -136,14 +136,14 @@ namespace Acme.BookStore
} }
```` ````
* **DTO** classes are used to **transfer data** between the *presentation layer* and the *application layer*. See the [Data Transfer Objects document](../../Data-Transfer-Objects.md) for more details. * **DTO**类被用来在 **基础设施层** 和 **应用层** **传递数据**.查看[DTO文档](../../Data-Transfer-Objects.md)查看更多信息.
* `BookDto` is used to transfer book data to the presentation layer in order to show the book information on the UI. * 为了在页面上展示书籍信息,`BookDto`被用来将书籍数据传递到基础设施层.
* `BookDto` is derived from the `AuditedEntityDto<Guid>` which has audit properties just like the `Book` class defined above. * `BookDto`继承自 `AuditedEntityDto<Guid>`.跟上面定义的`Book`类一样具有一些审计属性.
* `[AutoMapFrom(typeof(Book))]` is used to create AutoMapper mapping from the `Book` class to the `BookDto` class. In this way, you get automatic convertion of `Book` objects to `BookDto` objects (instead of manually copy all properties). * `[AutoMapFrom(typeof(Book))]`用来创建从`Book`类到`BookDto`的映射.使用这种方法.你可以将`Book`对象自动转换成`BookDto`对象(而不是手动复制所有的属性).
#### CreateUpdateBookDto #### CreateUpdateBookDto
Create a DTO class named `CreateUpdateBookDto` into the `Acme.BookStore.Application` project: 在`Acme.BookStore.Application`项目中创建一个名为`CreateUpdateBookDto`的DTO类:
````c# ````c#
using System; using System;
@ -171,12 +171,12 @@ namespace Acme.BookStore
} }
```` ````
* This DTO class is used to get book information from the user interface while creating or updating a book. * 这个DTO类在创建和更新书籍的时候被使用,用来从页面获取图书信息.
* It defines data annotation attributes (like `[Required]`) to define validations for the properties. DTOs are automatically validated by ABP. * 类中的属性定义了数据注解(如`[Required]`)用来定义有效性验证.ABP会自动校验DTO的数据有效性.
#### IBookAppService #### IBookAppService
Define an interface named `IBookAppService` for the book application service: 为应用服务定义一个名为 `IBookAppService` 的接口:
````C# ````C#
using System; using System;
@ -186,25 +186,27 @@ using Volo.Abp.Application.Services;
namespace Acme.BookStore namespace Acme.BookStore
{ {
public interface IBookAppService : public interface IBookAppService :
IAsyncCrudAppService< //Defines CRUD methods IAsyncCrudAppService< //定义了CRUD方法
BookDto, //Used to show books BookDto, //用来展示书籍
Guid, //Primary key of the book entity Guid, //Book实体的主键
PagedAndSortedResultRequestDto, //Used for paging/sorting on getting a list of books PagedAndSortedResultRequestDto, //获取书籍的时候用于分页和排序
CreateUpdateBookDto, //Used to create a new book CreateUpdateBookDto, //用于创建书籍
CreateUpdateBookDto> //Used to update a book CreateUpdateBookDto> //用户更新书籍
{ {
} }
} }
```` ````
* Defining interfaces for application services is <u>not required</u> by the framework. However, it's suggested as best practice. * 为应用服务定义接口不是必须的,不过,我们推荐这么做.
* `IAsyncCrudAppService` defines common **CRUD** methods: `GetAsync`, `GetListAsync`, `CreateAsync`, `UpdateAsync` and `DeleteAsync`. It's not required to extend it. Instead, you could inherit from the empty `IApplicationService` interface and define your own methods. * `IAsyncCrudAppService`中定义了基础的 **CRUD**方法:`GetAsync`, `GetListAsync`, `CreateAsync`, `UpdateAsync` 和 `DeleteAsync`.不需要扩展它.取而代之,你可以继承空的`IApplicationService`接口定义你自己的方法.
* There are some variations of the `IAsyncCrudAppService` where you can use a single DTO or separated DTOs for each method. * `IAsyncCrudAppService`有一些变体,你可以为每一个方法使用单个或者多个的DTO.(译者注:意思是类似EntityDto和UpdateEntityDto可以用同一个,也可以分别单独指定
)
#### BookAppService #### BookAppService
Implement the `IBookAppService` as named `BookAppService`: 创建 `BookAppService` 并实现 `IBookAppService`接口:
````C# ````C#
using System; using System;
@ -228,75 +230,76 @@ namespace Acme.BookStore
} }
```` ````
* `BookAppService` is derived from `AsyncCrudAppService<...>` which implements all the CRUD methods defined above. * `BookAppService`继承了`AsyncCrudAppService<...>`.`AsyncCrudAppService<...>`实现了上面定义的CRUD方法.
* `BookAppService` injects `IRepository<Book, Guid>` which is the default repository created for the `Book` entity. ABP automatically creates repositories for each aggregate root (or entity). See the [repository document](../../Repositories.md). * `BookAppService`注入了`IRepository<Book, Guid>`,`IRepository<Book, Guid>`是默认为`Book`创建的仓储.ABP会自动为每一个聚合根(或实体)创建仓储.参考[仓储](../../Repositories.md).
* `BookAppService` uses `IObjectMapper` to convert `Book` objects to `BookDto` objects and `CreateUpdateBookDto` objects to `Book` objects. The Startup template uses the [AutoMapper](http://automapper.org/) library as object mapping provider. You defined mappings using the `AutoMapFrom` and the `AutoMapTo` attributes above. See the [AutoMapper integration document](../../AutoMapper-Integration.md) for details. * `BookAppService`使用了 `IObjectMapper` 将`Book`转换成`BookDto`,将`CreateUpdateBookDto`转换成`Book`.启动模板中使用了[AutoMapper](http://automapper.org/)作为映射工具.你可以像上面那样使用`AutoMapFrom` 和 `AutoMapTo`定义映射.查看[AutoMapper继承](../../AutoMapper-Integration.md)获取更多信息.
### Auto API Controllers ### 自动生成API Controllers
You normally create **Controllers** to expose application services as **HTTP API** endpoints. Thus allowing browser or 3rd-party clients to call them via AJAX. 你通常需要创建 **Controllers** 将应用服务暴露为 **HTTP API**.这样浏览器或第三方客户端可以通过AJAX的方式访问它们.
ABP can **automagically** configures your application services as MVC API Controllers by convention. ABP可以 **自动地** 将应用服务转换成MVC API Controllers.
#### Swagger UI #### Swagger UI
The startup template is configured to run the [swagger UI](https://swagger.io/tools/swagger-ui/) using the [Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore) library. Run the application and enter `http://localhost:53929/swagger/` as URL on your browser. 启动模板使用了[Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore)库配置了[swagger UI](https://swagger.io/tools/swagger-ui/).运行程序并在浏览器中输入`http://localhost:53929/swagger/`.
You will see some built-in service endpoints as well as the `Book` service and its REST-style endpoints: 你会看到一些内置的接口和`Book`的接口,它们都是REST风格的:
![bookstore-swagger](images/bookstore-swagger.png) ![bookstore-swagger](images/bookstore-swagger.png)
### Dynamic JavaScript Proxies ### 动态JavaScript代理
It's common to call HTTP API endpoints via AJAX from the **JavaScript** side. You can use `$.ajax` or another tool to call the endpoints. However, ABP offers a better way. 通过AJAX的方式调用HTTP API接口是很常见的,你可以使用`$.ajax`或这其他的工具来调用接口.当然,ABP中提供了更好的方式.
ABP **dynamically** creates JavaScript **proxies** for all API endpoints. So, you can use any **endpoint** just like calling a **JavaScript function**. ABP **自动** 为所有的API接口创建了JavaScript **代理**.因此,你可以像调用 **JavaScript function**一样调用任何接口.
#### Testing in the Browser Developer Console #### 在浏览器的开发者控制台中测试接口
You can easily test the JavaScript proxy using your favorite browser's **Developer Console** now. Run the application again, open your browser's **developer tools** (shortcut: F12), switch to the **Console** tab, type the following code and press enter: 你可以使用你最爱的浏览器的 **开发者控制台** 中轻松测试JavaScript代理.运行程序,并打开浏览器的 **开发者工具**(快捷键:F12),切换到 **Console**,输入下面的代码并回车:
````js ````js
acme.bookStore.book.getList({}).done(function (result) { console.log(result); }); acme.bookStore.book.getList({}).done(function (result) { console.log(result); });
```` ````
* `acme.bookStore` is the namespace of the `BookAppService` converted to [camelCase](https://en.wikipedia.org/wiki/Camel_case). * `acme.bookStore`是`BookAppService`的命名空间,转换成了[驼峰命名](https://en.wikipedia.org/wiki/Camel_case).
* `book` is the conventional name for the `BookAppService` (removed AppService postfix and converted to camelCase). * `book`是`BookAppService`转换后的名字(去除了AppService后缀并转成了驼峰命名).
* `getList` is the conventional name for the `GetListAsync` method defined in the `AsyncCrudAppService` base class (removed Async postfix and converted to camelCase). * `getList`是定义在`AsyncCrudAppService`基类中的`GetListAsync`方法转换后的名字(去除了Async后缀并转成了驼峰命名).
* `{}` argument is used to send an empty object to the `GetListAsync` method which normally expects an object of type `PagedAndSortedResultRequestDto` which is used to send paging and sorting options to the server. * `{}`参数用来传递一个空的对象给`GetListAsync`方法.GetListAsync期望的参数是`PagedAndSortedResultRequestDto`类型,`PagedAndSortedResultRequestDto`类型中定义了分页和排序.
* `getList` function returns a `promise`. So, you can pass a callback to the `done` (or `then`) function to get the result from the server. * `getList`方法返回了一个`promise`.因此,你可以传递一个回调函数到`done`(或者`then`)方法中来获取服务返回的结果.
Running this code produces the following output: 运行这段代码会产生下面的输出:
![bookstore-test-js-proxy-getlist](images/bookstore-test-js-proxy-getlist.png) ![bookstore-test-js-proxy-getlist](images/bookstore-test-js-proxy-getlist.png)
You can see the **book list** returned from the server. You can also check the **network** tab of the developer tools to see the client to server communication: 你可以看到服务器返回的 **book list**.你还可以切换到开发者工具的 **network** 查看客户端和服务器的连接:
![bookstore-test-js-proxy-getlist-network](images/bookstore-test-js-proxy-getlist-network.png) ![bookstore-test-js-proxy-getlist-network](images/bookstore-test-js-proxy-getlist-network.png)
Let's **create a new book** using the `create` function: 我们使用`create`方法 **创建一本新书**:
````js ````js
acme.bookStore.book.create({ name: 'Foundation', type: 7, publishDate: '1951-05-24', price: 21.5 }).done(function (result) { console.log('successfully created the book with id: ' + result.id); }); acme.bookStore.book.create({ name: 'Foundation', type: 7, publishDate: '1951-05-24', price: 21.5 }).done(function (result) { console.log('successfully created the book with id: ' + result.id); });
```` ````
You should see a message in the console something like that: 你会看到控制台会显示类似这样的输出:
```` ````
successfully created the book with id: f3f03580-c1aa-d6a9-072d-39e75c69f5c7 successfully created the book with id: f3f03580-c1aa-d6a9-072d-39e75c69f5c7
```` ````
Check the `books` table in the database to see the new book row. You can try `get`, `update` and `delete` functions too. 检查数据库表`books`中的数据,你会发现多了一行新数据,你也可以尝试`get`, `update` 和 `delete`方法.
### 创建书籍页面
### Create the Books Page 现在我们来创建一些可见的可用的东西,我们使用[Razor Pages UI](https://docs.microsoft.com/en-us/aspnet/core/tutorials/razor-pages/razor-pages-start)代替经典的MVC.微软也推荐使用Razor Pages UI
It's time to create something visible and usable! Instead of classic MVC, we will use the new [Razor Pages UI](https://docs.microsoft.com/en-us/aspnet/core/tutorials/razor-pages/razor-pages-start) approach which is recommended by Microsoft.
Create a new `Books` folder under the `Pages` folder of the `Acme.BookStore.Web` project and add a new Razor Page named `Index.html`: 在 `Acme.BookStore.Web`项目的`Pages`文件夹下创建一个新的文件夹叫`Books`并添加一个名叫`Index.html`的Razor Page.
![bookstore-add-index-page](images/bookstore-add-index-page.png) ![bookstore-add-index-page](images/bookstore-add-index-page.png)
Open the `Index.cshtml` and change the content as shown below: 打开`Index.cshtml`并把内容修改成下面这样:
````html ````html
@page @page
@ -307,11 +310,11 @@ Open the `Index.cshtml` and change the content as shown below:
<h2>Books</h2> <h2>Books</h2>
```` ````
* Change the default inhertitance of the Razor View Page Model so it **inherits** from the `BookStorePageBase` class (instead of `PageModel`). The `BookStorePageBase` class which comes with the startup template and provides some shared properties/methods used by all pages. * 改变Razor View Page Model默认的继承,使页面的 **inherits** 来自`BookStorePageBase`类(代替`PageModel`).`BookStorePageBase`类来自启动模板并提供了一些公开的属性/方法,这些属性/方法可以被所有的页面使用.
#### Add Books Page to the Main Menu #### 将Books页面添加到主菜单
Open the `BookStoreMenuContributor` class in the `Menus` folder and add the following code to the end of the `ConfigureMainMenuAsync` method: 打开`Menus`文件夹中的 `BookStoreMenuContributor` 类,在`ConfigureMainMenuAsync`方法的底部添加如下代码:
````c# ````c#
context.Menu.AddItem( context.Menu.AddItem(
@ -320,13 +323,13 @@ context.Menu.AddItem(
); );
```` ````
#### Localizing the Menu Items #### 本地化菜单
Localization texts are located under the `Localization/BookStore` folder of the `Acme.BookStore.Domain` project: 本地化的文本在`Acme.BookStore.Domain`项目的`Localization/BookStore`文件夹中.
![bookstore-localization-files](images/bookstore-localization-files.png) ![bookstore-localization-files](images/bookstore-localization-files.png)
Open the `en.json` file and add localization texts for `Menu:BookStore` and `Menu:Books` keys: 打开`en.json`文件,为`Menu:BookStore` 和 `Menu:Books`添加本地化文本:
````json ````json
{ {
@ -339,22 +342,22 @@ Open the `en.json` file and add localization texts for `Menu:BookStore` and `Men
} }
```` ````
* ABP's localization system is built on [ASP.NET Core's standard localization](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization) system and extends it in many ways. See the [localization document](../../Localization.md) for details. * ABP的本地化功能建立在[ASP.NET Core's standard localization]((https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization))之上并增加了一些扩展.查看[本地化文档](../../Localization.md).
* Localization key names are arbitrary, you can set any name. We prefer to add `Menu` namespace for menu items to distinguish from other texts. If a text is not defined in the localization file, it **fallbacks** to the localization key (ASP.NET Core's standard behavior). * 本地化中key的名字是随便定义的,你可以随意命名.我们喜欢为菜单添加`Menu`命名空间,以区别于其他的文本.如果文本没有在本地化文件中定义,就会 **返回** 本地的化的key(ASP.NET Core的标准做法).
Run the application and see the menu items are added to the top bar: 运行程序就会看到菜单已经添加到了顶部:
![bookstore-menu-items](images/bookstore-menu-items.png) ![bookstore-menu-items](images/bookstore-menu-items.png)
When you click to the Books menu item, you are redirected to the new Books page. 点击菜单就会调转到新增书籍的页面.
#### Book List #### 书籍列表
We will use the [Datatables.net](https://datatables.net/) JQuery plugin to show list of tables on the page. Datatables can completely work via AJAX, so it is fast and provides a good user experience. Datatables plugin is configured in the startup template, so you can directly use it in any page without including any style or script file to your page. 我们会在页面上使用JQuery插件[Datatables.net](https://datatables.net/)来展示列表.Datatables可以完全通过AJAX工作,所以它很快而且有良好的用户体验.启动模板中已经配置好了Datatables,因此你可以在你的页面中直接使用,不需要引用样式和脚本文件.
##### Index.cshtml Changes ##### 修改Index.cshtml
Change the `Pages/Books/Index.cshtml` as following: 将`Pages/Books/Index.cshtml`改成下面的样子:
````html ````html
@page @page
@ -385,17 +388,17 @@ Change the `Pages/Books/Index.cshtml` as following:
</abp-card> </abp-card>
```` ````
* `abp-script` [tag helper](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro) is used to add external **scripts** to the page. It has many additional features compared to standard `script` tag. It handles **minification** and **versioning** for example. See the [bundling & minification document](../../AspNetCore/Bundling-Minification.md) for details. * `abp-script` [tag helper](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro)可以将添加外部的 **scripts**添加到页面中.它比标准的`script`标签多了很多额外的功能.它可以处理 **最小化**和 **版本**.查看[bundling & minification 文档](../../AspNetCore/Bundling-Minification.md)获取更多信息.
* `abp-card` and `abp-table` are **tag helpers** for Twitter Bootstrap's [card component](http://getbootstrap.com/docs/4.1/components/card/). There are many tag helpers in ABP to easily use most of the [bootstrap](https://getbootstrap.com/) components. You can also use regular HTML tags instead of these tag helpers, but using tag helpers reduces HTML code and prevents errors by help of the intellisense and compile time type checking. See the [tag helpers document](../../AspNetCore/Tag-Helpers.md). * `abp-card` 和 `abp-table` 是为Twitter Bootstrap的[card component](http://getbootstrap.com/docs/4.1/components/card/)封装的 **tag helpers**.ABP中有很多tag helpers,可以很方便的使用大多数[bootstrap](https://getbootstrap.com/)组件.你也可以使用原生的HTML标签代替tag helpers.使用tag helper可以通过智能提示和编译时类型检查减少HTML代码并防止错误.查看[tag helpers 文档](../../AspNetCore/Tag-Helpers.md).
* You can **localize** the column names in the localization file as you did for the menu items above. * 你可以像上面本地化菜单一样 **本地化** 列名.
##### Add a Script File #### 添加脚本文件
Create `index.js` JavaScript file under the `Pages/Books/` folder: 在`Pages/Books/`文件夹中创建 `index.js`文件
![bookstore-index-js-file](images/bookstore-index-js-file.png) ![bookstore-index-js-file](images/bookstore-index-js-file.png)
`index.js` content is shown below: `index.js`的内容如下:
````js ````js
$(function () { $(function () {
@ -412,15 +415,15 @@ $(function () {
}); });
```` ````
* `abp.libs.datatables.createAjax` is a helper function to adapt ABP's dynamic JavaScript API proxies to Datatable's format. * `abp.libs.datatables.createAjax`是帮助ABP的动态JavaScript API代理跟Datatable的格式相适应的辅助方法.
* `abp.libs.datatables.normalizeConfiguration` is another helper function. There's no requirment to use it, but it simplifies the datatables configuration by providing conventional values for missing options. * `abp.libs.datatables.normalizeConfiguration`是另一个辅助方法.不是必须的, 但是它通过为缺少的选项提供常规值来简化数据表配置.
* `acme.bookStore.book.getList` is the function to get list of books (you have seen it before). * `acme.bookStore.book.getList`是获取书籍列表的方法(上面已经介绍过了)
* See [Datatable's documentation](https://datatables.net/manual/) for more configuration options. * 查看 [Datatable's 文档](https://datatables.net/manual/) 了解更多配置项.
The final UI is shown below: 最终的页面如下:
![bookstore-book-list](images/bookstore-book-list.png) ![bookstore-book-list](images/bookstore-book-list.png)
### Next Part ### 下一章
See the [next part](Part-II.md) of this tutorial. 点击查看 [下一章](Part-II.md) 的介绍.

114
docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-II.md

@ -1,30 +1,30 @@
## ASP.NET Core MVC Tutorial - Part II ## ASP.NET Core MVC 教程 - 第二章
### About this Tutorial ### 关于本教程
This is the second part of the tutorial series. See all parts: 这是本教程所有章节中的第二章。下面是所有的章节:
* [Part I: Create the project and a book list page](Part-I.md) * [Part I: 创建项目和书籍列表页面](Part-I.md)
* **Part II: Create, Update and Delete books (this tutorial)** * **Part II: 创建,编辑,删除书籍(本章)**
* [Part III: Integration Tests](Part-III.md) * [Part III: 集成测试](Part-III.md)
You can download the **source code** of the application [from here](https://github.com/volosoft/abp/tree/master/samples/BookStore). 你可以从 [这里](https://github.com/volosoft/abp/tree/master/samples/BookStore) 下载本程序的**源码**。
### Creating a New Book ### 新增 Book 实体
In this section, you will learn how to create a new modal dialog form to create a new book. The result dialog will be like that: 通过本节, 你将会了解如何创建一个 modal form 来实现新增书籍的功能。 最终成果如下图所示:
![bookstore-create-dialog](images/bookstore-create-dialog.png) ![bookstore-create-dialog](images/bookstore-create-dialog.png)
#### Create the Modal Form #### 新建 modal form
Create a new razor page, named `CreateModal.cshtml` under the `Pages/Books` folder of the `Acme.BookStore.Web` project: 在 `Acme.BookStore.Web` 项目的 `Pages/Books` 目录下新建一个 `CreateModal.cshtml` Razor页面:
![bookstore-add-create-dialog](images/bookstore-add-create-dialog.png) ![bookstore-add-create-dialog](images/bookstore-add-create-dialog.png)
##### CreateModal.cshtml.cs ##### CreateModal.cshtml.cs
Open the `CreateModal.cshtml.cs` file (`CreateModalModel` class) and replace with the following code: 展开 `CreateModal.cshtml`,打开 `CreateModal.cshtml.cs` 代码文件,用如下代码替换 `CreateModalModel` 类的实现:
````C# ````C#
using System.Threading.Tasks; using System.Threading.Tasks;
@ -53,13 +53,13 @@ namespace Acme.BookStore.Pages.Books
} }
```` ````
* This class is derived from the `BookStorePageModelBase` instead of standard `PageModel`. `BookStorePageModelBase` inherits the `PageModel` and adds some common properties/methods those can be used by your page model classes. * 这个类继承了 `BookStorePageModelBase` 而非默认的 `PageModel`。 `BookStorePageModelBase` 继承了 `PageModel` 并且添加了一些Razor页面模型通用的属性和方法。
* `[BindProperty]` attribute on the `Book` property binds post request data to this property. * 该类在 `Book` 属性上标记的 `[BindProperty]` 特性绑定了post请求提交上来的数据。
* This class simply injects the `IBookAppService` in its constructor and calls the `CreateAsync` method in the `OnPostAsync` handler. * 该类通过构造函数注入了 `IBookAppService` 应用服务,并且在 `OnPostAsync` 方法中调用了服务的 `CreateAsync` 方法。
##### CreateModal.cshtml ##### CreateModal.cshtml
Open the `CreateModal.cshtml` file and paste the code below: 打开 `CreateModal.cshtml` 文件并粘贴如下代码:
````html ````html
@page @page
@ -80,14 +80,14 @@ Open the `CreateModal.cshtml` file and paste the code below:
</abp-dynamic-form> </abp-dynamic-form>
```` ````
* This modal uses `abp-dynamic-form` tag helper to automatically create the form from the `CreateBookViewModel` class. * 这个 modal 使用 `abp-dynamic-form` Tag Helper 根据 `CreateBookViewModel` 类自动构建了表单。
* `abp-model` attribute indicates the model object, the `Book` property in this case. * `abp-model` 指定了 `Book` 属性为模型对象。
* `data-ajaxForm` attribute makes the form submitting via AJAX, instead of a classic page post. * `data-ajaxForm` 设置了表单通过AJAX提交。
* `abp-form-content` tag helper is a placeholder to render the form controls (this is optional and needed only if you added some other content in the `abp-dynamic-form` tag, just like in this view). * `abp-form-content` tag helper 作为表单控件渲染位置的占位符 (这是可选的,只有你在 `abp-dynamic-form` 中像本示例这样添加了其他内容才需要).
#### Add the "New book" Button #### 添加 "New book" 按钮
Open the `Pages/Books/Index.cshtml` and change the `abp-card-header` tag as shown below: 打开 `Pages/Books/Index.cshtml` 并按如下代码修改 `abp-card-header` :
````html ````html
<abp-card-header> <abp-card-header>
@ -105,11 +105,11 @@ Open the `Pages/Books/Index.cshtml` and change the `abp-card-header` tag as show
</abp-card-header> </abp-card-header>
```` ````
Just added a **New book** button to the **top right** of the table: 如下图所示,只是在表格 **右上方** 添加了 **New book** 按钮:
![bookstore-new-book-button](images/bookstore-new-book-button.png) ![bookstore-new-book-button](images/bookstore-new-book-button.png)
Open the `wwwroot/pages/books/index.js` and add the following code just after the datatable configuration: 打开 `wwwroot/pages/books/index.js` 在datatable配置代码后面添加如下代码:
````js ````js
var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal'); var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal');
@ -124,19 +124,19 @@ $('#NewBookButton').click(function (e) {
}); });
```` ````
* `abp.ModalManager` is a helper class to open and manage modals in the client side. It internally uses Twitter Bootstrap's standard modal, but abstracts many details by providing a simple API. * `abp.ModalManager` 是一个在客户端打开和管理modal的辅助类。它基于Twitter Bootstrap的标准modal组件通过简化的API抽象隐藏了许多细节.
Now, you can **run the application** and add new books using the new modal form. 现在,你可以 **运行程序** 通过新的 modal form 来创建书籍了。
### Updating An Existing Book ### 编辑更新已存在的 Book 实体
Create a new razor page, named `EditModal.cshtml` under the `Pages/Books` folder of the `Acme.BookStore.Web` project: 在 `Acme.BookStore.Web` 项目的 `Pages/Books` 目录下新建一个名叫 `EditModal.cshtml` 的Razor页面:
![bookstore-add-edit-dialog](images/bookstore-add-edit-dialog.png) ![bookstore-add-edit-dialog](images/bookstore-add-edit-dialog.png)
#### EditModal.cshtml.cs #### EditModal.cshtml.cs
Open the `EditModal.cshtml.cs` file (`EditModalModel` class) and replace with the following code: 展开 `EditModal.cshtml`,打开 `EditModal.cshtml.cs` 文件( `EditModalModel` 类) 并替换成以下代码:
````C# ````C#
using System; using System;
@ -176,13 +176,13 @@ namespace Acme.BookStore.Pages.Books
} }
```` ````
* `[HiddenInput]` and `[BindProperty]` are standard ASP.NET Core MVC attributes. Used `SupportsGet` to be able to get Id value from query string parameter of the request. * `[HiddenInput]` 和 `[BindProperty]` 是标准的 ASP.NET Core MVC 特性。这里启用 `SupportsGet` 从Http请求的查询字符串中获取Id的值。
* Mapped `BookDto` (received from the `BookAppService.GetAsync`) to `CreateUpdateBookDto` in the `GetAsync` method. * 在 `OnGetAsync` 方法中,将 `BookAppService.GetAsync` 方法返回的 `BookDto` 映射成 `CreateUpdateBookDto` 并赋值给Book属性。
* The `OnPostAsync` simply uses `BookAppService.UpdateAsync` to update the entity. * `OnPostAsync` 方法直接使用 `BookAppService.UpdateAsync` 来更新实体。
#### CreateUpdateBookDto #### CreateUpdateBookDto
In order to perform `BookDto` to `CreateUpdateBookDto` object mapping, change the `CreateUpdateBookDto` class as shown below: 为了执行从 `BookDto` 到 `CreateUpdateBookDto` 的对象映射, 按如下所示修改 `CreateUpdateBookDto`类:
````C# ````C#
using System; using System;
@ -211,11 +211,11 @@ namespace Acme.BookStore
} }
```` ````
* Just added the `[AutoMapFrom(typeof(BookDto))]` attribute to create the mapping. * 仅仅是添加 `[AutoMapFrom(typeof(BookDto))]` 特性就可以创建上述映射关系。
#### EditModal.cshtml #### EditModal.cshtml
Replace `EditModal.cshtml` content with the following content: 将 `EditModal.cshtml` 页面内容替换成如下代码:
````html ````html
@page @page
@ -238,18 +238,18 @@ Replace `EditModal.cshtml` content with the following content:
</abp-dynamic-form> </abp-dynamic-form>
```` ````
This page is very similar to the `CreateModal.cshtml` except; 除了以下几点,这个页面内容和 `CreateModal.cshtml` 非常相似:
* It includes an `abp-input` for the `Id` property to store id of the editing book. * 此页面包含了一个 `abp-input` 以保存所编辑book实体的 `Id` 属性。
* It uses `Books/EditModal` as the post URL and *Update* text as the modal header. * 此页面指定的post地址是 `Books/EditModal` ,并用文本 *Update* 作为 modal 标题。
#### Add "Actions" Dropdown to the Table #### 为表格添加 "操作(Actions)" 下拉菜单
We will add a dropdown button ("Actions") for each row of the table. The final UI looks like this: 我们将为表格每行添加下拉按钮 ("Actions") 。 最终效果如下:
![bookstore-books-table-actions](images/bookstore-books-table-actions.png) ![bookstore-books-table-actions](images/bookstore-books-table-actions.png)
Open the `Pages/Books/Index.cshtml` page and change the table section as shown below: 打开 `Pages/Books/Index.cshtml` 页面,并按下方所示修改表格部分的代码:
````html ````html
<abp-table striped-rows="true" id="BooksTable"> <abp-table striped-rows="true" id="BooksTable">
@ -266,9 +266,9 @@ Open the `Pages/Books/Index.cshtml` page and change the table section as shown b
</abp-table> </abp-table>
```` ````
* Just added a new `th` tag for the "Actions". * 只是为"Actions"增加了一个 `th` 标签.
Open the `wwwroot/pages/books/index.js` and replace the content as below: 打开 `wwwroot/pages/books/index.js` 并用以下内容进行替换:
````js ````js
$(function () { $(function () {
@ -318,16 +318,16 @@ $(function () {
}); });
```` ````
* Used `abp.localization.getResource('BookStore')` to be able to use the same localization texts defined on the server side. * 通过 `abp.localization.getResource('BookStore')` 可以在客户端使用服务器端定义的相同的本地化语言文本。
* Added a new `ModalManager` named `editModal` to open the edit modal dialog. * 定义 `editModal` 为 `ModalManager` 来打开编辑用的 modal 对话框。
* Added a new column at the beginning of the `columnDefs` section. This column is used for the "Actions" dropdown button. * 在 `columnDefs` 起始处新增一列作为 "Actions" 下拉按钮。
* "Edit" action simply calls `editModal.open` to open the edit dialog. * "Edit" 操作只是简单调用 `editModal.open` 来打开编辑对话框。
You can run the application and edit any book by selecting the edit action. 现在,你可以运行程序,通过编辑操作来更新任一个book实体。
### Deleting an Existing Book ### 删除一个已有的Book实体
Open the `wwwroot/pages/books/index.js` and add a new item to the `rowAction` `items`: 打开 `wwwroot/pages/books/index.js` 文件,在 `rowAction` `items` 下新增一项:
````js ````js
{ {
@ -346,11 +346,11 @@ Open the `wwwroot/pages/books/index.js` and add a new item to the `rowAction` `i
} }
```` ````
* `confirmMessage` option is used to ask a confirmation question before executing the `action`. * `confirmMessage` 用来在实际执行 `action` 之前向用户进行确认。
* Used `acme.bookStore.book.delete` javascript proxy function to perform an AJAX request to delete a book. * 通过javascript代理方法 `acme.bookStore.book.delete` 执行一个AJAX请求来删除一个book实体。
* `abp.notify.info` is used to show a toastr notification just after the deletion. * `abp.notify.info` 用来提示用户操作成功。
The final `index.js` content is shown below: 最终的 `index.js` 文件内容如下所示:
````js ````js
$(function () { $(function () {
@ -414,8 +414,8 @@ $(function () {
}); });
```` ````
Run the application and try to delete a book. 运行程序并尝试删除一个book实体。
### Next Part ### 下一章
See the [next part](Part-III.md) of this tutorial. 查看本教程的 [下一章](Part-III.md) 。

52
docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-III.md

@ -2,32 +2,32 @@
### About this Tutorial ### About this Tutorial
This is the third part of the tutorial series. See all parts: 这是本教程所有章节中的第三章。下面是所有的章节:
- [Part I: Create the project and a book list page](Part-I.md) - [Part I: 创建项目和书籍列表页面](Part-I.md)
- [Part II: Create, Update and Delete books](Part-II.md) - [Part II: 创建,编辑,删除书籍](Part-II.md)
- **Part III: Integration Tests (this tutorial)** - **Part III: 集成测试(本章)**
You can download the **source code** of the application [from here](https://github.com/volosoft/abp/tree/master/samples/BookStore). 你可以从 [这里](https://github.com/volosoft/abp/tree/master/samples/BookStore) 下载本程序的**源码**。
### Test Projects in the Solution ### 解决方案中的测试项目
There are two test projects in the solution: 本解决方案中有两个测试项目:
![bookstore-test-projects](images/bookstore-test-projects.png) ![bookstore-test-projects](images/bookstore-test-projects.png)
* `Acme.BookStore.Application.Tests` is for unit & integration tests. You can write tests for application service methods. It uses **EF Core SQLite in-memory** database. * `Acme.BookStore.Application.Tests` 项目用于单元测试和集成测试。你可以在这个项目中为Application Service方法写测试代码。这个项目使用了 **EF Core SQLite in-memory** 数据库。
* `Acme.BookStore.Web.Tests` is for full stack integration tests including the web layer. So, you can write tests for UI pages too. * `Acme.BookStore.Web.Tests` 项目用于包含Web层的完整集成测试。所以,你也可以在这里写关于UI页面的测试。
Test projects use the following libraries for testing: 测试项目使用了以下库:
* [xunit](https://xunit.github.io/) as the main test framework. * [xunit](https://xunit.github.io/) 作为主测试框架。
* [Shoudly](http://shouldly.readthedocs.io/en/latest/) as an assertion library. * [Shoudly](http://shouldly.readthedocs.io/en/latest/) 作为断言库。
* [NSubstitute](http://nsubstitute.github.io/) as a mocking library. * [NSubstitute](http://nsubstitute.github.io/) 作为模拟库。
### Adding Test Data ### 添加测试用数据
Startup template contains the `BookStoreTestDataBuilder` class in the `Acme.BookStore.Application.Tests` project that creates some data to run tests on. It's shown below: 起始模板在 `Acme.BookStore.Application.Tests` 项目中包含了 `BookStoreTestDataBuilder` 类,用于创建一些测试用数据。 相关代码如下所示:
````C# ````C#
using System.Threading.Tasks; using System.Threading.Tasks;
@ -59,10 +59,10 @@ namespace Acme.BookStore
} }
```` ````
* It simply uses `IIdentityDataSeeder` which is implemented by the identity module and creates an admin role and admin user. You can use them in your tests. * 这里直接使用了identity模块实现的 `IIdentityDataSeeder` 接口,创建了一个admin角色和admin用户。你同样可以在你的测试代码中直接使用这些代码。
* You can add new test data in the `BuildInternalAsync` method. * 你可以在 `BuildInternalAsync` 方法中添加你自己的测试数据。
Change the `BookStoreTestDataBuilder` class as show below: 按下方所示修改 `BookStoreTestDataBuilder` 类:
````C# ````C#
using System; using System;
@ -122,11 +122,11 @@ namespace Acme.BookStore
} }
```` ````
* Injected `IRepository<Book, Guid>` and used it in the `BuildInternalAsync` to create two book entities. * 通过构造函数注入 `IRepository<Book, Guid>`,在 `BuildInternalAsync` 方法中用它创建两个book实体。
### Testing the BookAppService ### 测试 BookAppService
Create a test class named `BookAppService_Tests` in the `Acme.BookStore.Application.Tests` project: 在 `Acme.BookStore.Application.Tests` 项目中创建一个名叫 `BookAppService_Tests` 的测试类:
````C# ````C#
using System.Threading.Tasks; using System.Threading.Tasks;
@ -161,9 +161,9 @@ namespace Acme.BookStore
} }
```` ````
* `Should_Get_List_Of_Books` test simply uses `BookAppService.GetListAsync` method to get and check the list of users. * 测试方法 `Should_Get_List_Of_Books` 直接使用 `BookAppService.GetListAsync` 方法来获取用户列表,并执行检查。
Add a new test that creates a valid new book: 新增测试方法,用以测试创建一个合法book实体的场景:
````C# ````C#
[Fact] [Fact]
@ -186,7 +186,7 @@ public async Task Should_Create_A_Valid_Book()
} }
```` ````
Add a new test that tries to create an invalid book and fails: 新增测试方法,用以测试创建一个非法book实体失败的场景:
````C# ````C#
[Fact] [Fact]
@ -210,8 +210,8 @@ public async Task Should_Not_Create_A_Book_Without_Name()
} }
```` ````
* Since the `Name` is empty, ABP throws an `AbpValidationException`. * 由于 `Name` 是空值, ABP 抛出一个 `AbpValidationException` 异常。
### Testing Web Pages ### 测试 Web 页面
TODO TODO
Loading…
Cancel
Save