Browse Source

Revert "Merge branch 'dev' into Cotur/Blogging"

This reverts commit d358e80ecb.
pull/3720/head
Ahmet 6 years ago
parent
commit
cccffb6240
  1. 30
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
  2. 6
      common.DotSettings
  3. 2
      common.props
  4. 8
      docs/en/CLI.md
  5. 2
      docs/en/Entity-Framework-Core-Migrations.md
  6. 10
      docs/en/Getting-Started.md
  7. 52
      docs/en/Localization.md
  8. 8
      docs/en/Object-Extensions.md
  9. 159
      docs/en/Tutorials/Part-1.md
  10. 527
      docs/en/Tutorials/Part-2.md
  11. 4
      docs/en/Tutorials/Part-3.md
  12. BIN
      docs/en/Tutorials/images/bookstore-angular-file-tree.png
  13. BIN
      docs/en/Tutorials/images/generate-proxy-command.png
  14. BIN
      docs/en/Tutorials/images/generated-proxies.png
  15. 12
      docs/en/UI/Angular/Config-State.md
  16. 185
      docs/en/UI/Angular/Confirmation-Service.md
  17. 2
      docs/en/UI/Angular/Http-Requests.md
  18. 199
      docs/en/UI/Angular/Modifying-the-Menu.md
  19. 2
      docs/en/UI/Angular/Permission-Management.md
  20. 158
      docs/en/UI/Angular/Toaster-Service.md
  21. BIN
      docs/en/UI/Angular/images/confirmation.png
  22. BIN
      docs/en/UI/Angular/images/navigation-menu-after-patching.png
  23. BIN
      docs/en/UI/Angular/images/navigation-menu-search-input.png
  24. BIN
      docs/en/UI/Angular/images/navigation-menu-via-app-routing.png
  25. BIN
      docs/en/UI/Angular/images/navigation-menu-via-config-state.png
  26. BIN
      docs/en/UI/Angular/images/toast.png
  27. 22
      docs/en/UI/Common/Utils/Linked-List.md
  28. 27
      docs/en/docs-nav.json
  29. 4
      docs/zh-Hans/API/Auto-API-Controllers.md
  30. 2
      docs/zh-Hans/Application-Services.md
  31. 2
      docs/zh-Hans/AspNetCore/Tag-Helpers/Buttons.md
  32. 32
      docs/zh-Hans/Audit-Logging.md
  33. 2
      docs/zh-Hans/Authorization.md
  34. 4
      docs/zh-Hans/Background-Workers.md
  35. 14
      docs/zh-Hans/Blog-Posts/2019-02-22/Post.md
  36. 12
      docs/zh-Hans/Blog-Posts/2019-06-19 v0_18_Release/Post.md
  37. 4
      docs/zh-Hans/Blog-Posts/2019-08-16 v0_19_Release/Post.md
  38. 4
      docs/zh-Hans/Blog-Posts/2019-09-25 v0_21_Release/Post.md
  39. 16
      docs/zh-Hans/Blog-Posts/2020-03-19 v2_3_Release/Post.md
  40. 20
      docs/zh-Hans/CLI.md
  41. 2
      docs/zh-Hans/Customizing-Application-Modules-Overriding-Services.md
  42. 6
      docs/zh-Hans/Entities.md
  43. 14
      docs/zh-Hans/Entity-Framework-Core-Migrations.md
  44. 6
      docs/zh-Hans/Entity-Framework-Core-MySQL.md
  45. 2
      docs/zh-Hans/Entity-Framework-Core-Other-DBMS.md
  46. 4
      docs/zh-Hans/Entity-Framework-Core-PostgreSQL.md
  47. 4
      docs/zh-Hans/Entity-Framework-Core-SQLite.md
  48. 10
      docs/zh-Hans/Entity-Framework-Core.md
  49. 9
      docs/zh-Hans/Getting-Started-Angular-Template.md
  50. 4
      docs/zh-Hans/Getting-Started-AspNetCore-Application.md
  51. 104
      docs/zh-Hans/Getting-Started-AspNetCore-MVC-Template.md
  52. 8
      docs/zh-Hans/Getting-Started-With-Startup-Templates.md
  53. 416
      docs/zh-Hans/Getting-Started.md
  54. 2
      docs/zh-Hans/Index.md
  55. 52
      docs/zh-Hans/Localization.md
  56. 12
      docs/zh-Hans/Modules/Docs.md
  57. 108
      docs/zh-Hans/Object-Extensions.md
  58. 4
      docs/zh-Hans/Object-To-Object-Mapping.md
  59. 10
      docs/zh-Hans/Options.md
  60. 24
      docs/zh-Hans/Samples/Microservice-Demo.md
  61. 2
      docs/zh-Hans/Settings.md
  62. 3
      docs/zh-Hans/Specifications.md
  63. 4
      docs/zh-Hans/Startup-Templates/Application.md
  64. 2
      docs/zh-Hans/Startup-Templates/Index.md
  65. 8
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-I.md
  66. 4
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-III.md
  67. 0
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-add-create-dialog-v2.png
  68. 0
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-add-edit-dialog.png
  69. 0
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-add-index-page-v2.png
  70. 0
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-appservice-tests.png
  71. BIN
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-book-list-2.png
  72. BIN
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-book-list.png
  73. 0
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-books-table-actions.png
  74. 0
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-books-table.png
  75. 0
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-create-dialog-2.png
  76. 0
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-create-dialog.png
  77. BIN
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-create-template.png
  78. 0
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-homepage.png
  79. 0
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-index-js-file-v2.png
  80. BIN
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-localization-files-v2.png
  81. 0
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-menu-items.png
  82. BIN
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-new-book-button.png
  83. BIN
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-pmc-add-book-migration-v2.png
  84. 0
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-pmc-add-book-migration.png
  85. BIN
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-swagger.png
  86. 0
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-test-js-proxy-getlist-network.png
  87. 0
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-test-js-proxy-getlist.png
  88. 0
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-test-projects-v2.png
  89. 0
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-user-management.png
  90. 0
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-visual-studio-solution-v3.png
  91. 1078
      docs/zh-Hans/Tutorials/Part-1.md
  92. 1349
      docs/zh-Hans/Tutorials/Part-2.md
  93. 198
      docs/zh-Hans/Tutorials/Part-3.md
  94. BIN
      docs/zh-Hans/Tutorials/images/bookstore-actions-buttons.png
  95. BIN
      docs/zh-Hans/Tutorials/images/bookstore-angular-file-tree.png
  96. BIN
      docs/zh-Hans/Tutorials/images/bookstore-book-list-2.png
  97. BIN
      docs/zh-Hans/Tutorials/images/bookstore-book-list.png
  98. BIN
      docs/zh-Hans/Tutorials/images/bookstore-confirmation-popup.png
  99. BIN
      docs/zh-Hans/Tutorials/images/bookstore-create-project-angular.png
  100. BIN
      docs/zh-Hans/Tutorials/images/bookstore-create-project-mvc.png

30
abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json

@ -13,11 +13,8 @@
"Permission:Edit": "Edit",
"Permission:Delete": "Delete",
"Permission:Create": "Create",
"Permission:Accounting": "Accounting",
"Permission:Accounting:Quotation": "Quotation",
"Permission:Accounting:Invoice": "Invoice",
"Menu:Organizations": "Organizations",
"Menu:Accounting": "Accounting",
"Menu:Accounting": "Accounting",
"Menu:Packages": "Packages",
"NpmPackageDeletionWarningMessage": "This NPM Package will be deleted. Do you confirm that?",
"NugetPackageDeletionWarningMessage": "This Nuget Package will be deleted. Do you confirm that?",
@ -97,28 +94,13 @@
"UsernameOrEmail": "Username or email",
"UsernameOrEmailPlaceholder": "Username or email...",
"Member": "Member",
"PurchaseOrderNo": "Purchase order no",
"QuotationDate": "Quotation date",
"CompanyName": "Company name",
"CompanyAddress": "Company address",
"QuotationPurchasedOrderNo": "Quotation Purchased Order No",
"QuotationTime": "Quotation Time",
"CompanyName": "Company Name",
"CompanyAddress": "Company Address",
"Price": "Price",
"DiscountText": "Discount text",
"DiscountQuantity": "Discount quantity",
"DiscountPrice": "Discount price",
"Quotation": "Quotation",
"ExtraText": "Extra Text",
"ExtraAmount": "Extra Amount",
"DownloadQuotation": "Download Quotation",
"Invoice": "Invoice",
"TaxNumber": "Tax Number",
"InvoiceNumber": "Invoice Number",
"InvoiceDate": "Invoice Date",
"Quantity": "Quantity",
"AddProduct": "Add Product",
"AddProductWarning": "You need to add product!",
"TotalPrice": "Total Price",
"Generate": "Generate",
"MissingQuantityField": "The quantity field is required!",
"MissingPriceField": "The Price field is required!"
"DownloadQuotation": "Download Quotation"
}
}

6
common.DotSettings

@ -20,11 +20,5 @@
<s:String x:Key="/Default/CodeStyle/Generate/=Overrides/Options/=Async/@EntryIndexedValue">False</s:String>
<s:String x:Key="/Default/CodeStyle/Generate/=Overrides/Options/=Mutable/@EntryIndexedValue">False</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SQL/@EntryIndexedValue">SQL</s:String>
<s:Boolean x:Key="/Default/Environment/TypeNameHintsOptions/HideTypeNameHintsWhenTypeNameIsEvidentFromVariableName/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/Environment/TypeNameHintsOptions/ShowMethodReturnTypeNameHints/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/Environment/TypeNameHintsOptions/ShowTypeNameHintsForImplicitlyTypedVariables/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/Environment/TypeNameHintsOptions/ShowTypeNameHintsForLambdaExpressionParameters/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/Environment/TypeNameHintsOptions/ShowTypeNameHintsForLinqQueryRangeVariables/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/Environment/TypeNameHintsOptions/ShowTypeNameHintsForPatternMatchingExpressions/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Volo/@EntryIndexedValue">True</s:Boolean>
</wpf:ResourceDictionary>

2
common.props

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

8
docs/en/CLI.md

@ -135,8 +135,6 @@ abp update [options]
* `--include-previews` or `-p`: Includes preview, beta and rc packages while checking the latest versions.
* `--npm`: Only updates NPM packages.
* `--nuget`: Only updates NuGet packages.
* `--solution-path` or `-sp`: Specify the solution path. Use the current directory by default
* `--solution-name` or `-sn`: Specify the solution name. Search `*.sln` files in the directory by default.
### switch-to-preview
@ -172,11 +170,7 @@ Some features of the CLI requires to be logged in to abp.io platform. To login w
abp login <username>
```
```bash
abp login <username> -p <password>
```
Notice that, a new login with an already active session, overwrites the previous session.
Notice that, a new login with an already active session, will kill the previous session and creates a new one.
### logout

2
docs/en/Entity-Framework-Core-Migrations.md

@ -546,8 +546,6 @@ Entity extension system solves the main problem of the extra properties: It can
All you need to do is to use the `ObjectExtensionManager` to define the extra property as explained above, in the `AppRole` example. Then you can continue to use the same `GetProperty` and `SetProperty` methods defined above to get/set the related property on the entity, but this time stored as a separate field in the database.
See the [entity extension system](Customizing-Application-Modules-Extending-Entities.md) for details.
###### Creating a New Table
Instead of creating a new entity and mapping to the same table, you can also create **your own table** to store your properties. You typically duplicate some values of the original entity. For example, you can add `Name` field to your own table which is a duplication of the `Name` field in the original table.

10
docs/en/Getting-Started.md

@ -25,12 +25,6 @@ The following tools should be installed on your development machine:
* [Node v12+](https://nodejs.org)
* [Yarn v1.19+](https://classic.yarnpkg.com/)
{{ if Tiered == "Yes" }}
* [Redis](https://redis.io/): The applications use Redis as as [distributed cache](../Caching.md). So, you need to have Redis installed & running.
{{ end }}
> You can use another editor instead of Visual Studio as long as it supports .NET Core and ASP.NET Core.
@ -406,8 +400,8 @@ Enter **admin** as the username and **1q2w3E*** as the password to login to the
The application is up and running. You can continue to develop your application based on this startup template.
> The [application startup template](Startup-Templates/Application.md) includes the TenantManagement and Identity modules.
> The [application startup template](startup-templates/application/index.md) includes the TenantManagement and Identity modules.
## What's next?
[Application development tutorial](Tutorials/Part-1.md)
[Application development tutorial](tutorials/book-store/part-1.md)

52
docs/en/Localization.md

@ -87,21 +87,6 @@ A JSON localization file content is shown below:
* Every localization file should define the `culture` code for the file (like "en" or "en-US").
* `texts` section just contains key-value collection of the localization strings (keys may have spaces too).
### Default Resource
`AbpLocalizationOptions.DefaultResourceType` can be set to a resource type, so it is used when the localization resource was not specified:
````csharp
Configure<AbpLocalizationOptions>(options =>
{
options.DefaultResourceType = typeof(TestResource);
});
````
> The [application startup template](Startup-Templates/Application.md) sets `DefaultResourceType` to the localization resource of the application.
See the *Client Side* section below for a use case.
### Short Localization Resource Name
Localization resources are also available in the client (JavaScript) side. So, setting a short name for the localization resource makes it easy to use localization texts. Example:
@ -181,10 +166,6 @@ public class MyService
}
````
##### Format Arguments
Format arguments can be passed after the localization key. If your message is `Hello {0}, welcome!`, then you can pass the `{0}` argument to the localizer like `_localizer["HelloMessage", "John"]`
#### Simplest Usage In A Razor View/Page
````c#
@ -199,47 +180,18 @@ Refer to the [Microsoft's localization documentation](https://docs.microsoft.com
ABP provides JavaScript services to use the same localized texts in the client side.
#### getResource
`abp.localization.getResource` function is used to get a localization resource:
Get a localization resource:
````js
var testResource = abp.localization.getResource('Test');
````
Then you can localize a string based on this resource:
Localize a string:
````js
var str = testResource('HelloWorld');
````
#### localize
`abp.localization.localize` function is a shortcut where you can both specify the text name and the resource name:
````js
var str = abp.localization.localize('HelloWorld', 'Test');
````
`HelloWorld` is the text to localize, where `Test` is the localization resource name here.
If you don't specify the localization resource name, it uses the default localization resource defined on the `AbpLocalizationOptions` (see the *Default Resource* section above). Example:
````js
var str = abp.localization.localize('HelloWorld'); //uses the default resource
````
##### Format Arguments
If your localized string contains arguments, like `Hello {0}, welcome!`, you can pass arguments to the localization methods. Examples:
````js
var str1 = abp.localization.getResource('Test')('HelloWelcomeMessage', 'John');
var str2 = abp.localization.localize('HelloWorld', 'Test', 'John');
````
Both of the samples above produce the output `Hello John, welcome!`.
## See Also
* [Localization in Angular UI](UI/Angular/Localization.md)

8
docs/en/Object-Extensions.md

@ -197,8 +197,8 @@ ObjectExtensionManager.Instance
"SocialSecurityNumber",
options =>
{
options.Attributes.Add(new RequiredAttribute());
options.Attributes.Add(
options.ValidationAttributes.Add(new RequiredAttribute());
options.ValidationAttributes.Add(
new StringLengthAttribute(32) {
MinimumLength = 6
}
@ -248,12 +248,12 @@ ObjectExtensionManager.Instance
objConfig.AddOrUpdateProperty<string>("Password", propertyConfig =>
{
propertyConfig.Attributes.Add(new RequiredAttribute());
propertyConfig.ValidationAttributes.Add(new RequiredAttribute());
});
objConfig.AddOrUpdateProperty<string>("PasswordRepeat", propertyConfig =>
{
propertyConfig.Attributes.Add(new RequiredAttribute());
propertyConfig.ValidationAttributes.Add(new RequiredAttribute());
});
//Write a common validation logic works on multiple properties

159
docs/en/Tutorials/Part-1.md

@ -38,7 +38,7 @@ Create a new project named `Acme.BookStore` where `Acme` is the company name and
#### Create the project
By running the below command, it creates a new ABP project with the database provider `{{DB_Text}}` and UI option `{{UI_Value}}`. To see the other CLI options, check out [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) document.
By running the below command, it creates a new ABP project with the database provider `{{DB_Text}}` and UI option `MVC`. To see the other CLI options, check out [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) document.
```bash
abp new Acme.BookStore --template app --database-provider {{DB}} --ui {{UI_Text}} --mobile none
@ -61,7 +61,7 @@ After creating the project, you need to apply the initial migrations and create
To run the project, right click to the {{if UI == "MVC"}} `Acme.BookStore.Web`{{end}} {{if UI == "NG"}} `Acme.BookStore.HttpApi.Host` {{end}} project and click **Set As StartUp Project**. And run the web project by pressing **CTRL+F5** (*without debugging and fast*) or press **F5** (*with debugging and slow*). {{if UI == "NG"}}You will see the Swagger UI for BookStore API.{{end}}
Further information, see the [running the application section](../Getting-Started?UI={{UI}}#run-the-application).
Further information, see the [running the application section](../../Getting-Started-{{if UI == "NG"}}Angular{{else}}AspNetCore-MVC{{end}}-Template#running-the-application).Getting-Started-AspNetCore-MVC-Template#running-the-application
![Set as startup project](./images/bookstore-start-project-{{UI_Text}}.png)
@ -335,7 +335,7 @@ INSERT INTO AppBooks (Id,CreationTime,[Name],[Type],PublishDate,Price) VALUES
### Create the application service
The next step is to create an [application service](../Application-Services.md) to manage the books which will allow us the four basic functions: creating, reading, updating and deleting. Application layer is separated into two projects:
The next step is to create an [application service](../../Application-Services.md) to manage the books which will allow us the four basic functions: creating, reading, updating and deleting. Application layer is separated into two projects:
* `Acme.BookStore.Application.Contracts` mainly contains your `DTO`s and application service interfaces.
* `Acme.BookStore.Application` contains the implementations of your application services.
@ -874,52 +874,97 @@ We'll see **book-list works!** text on the books page:
Run the following command in the terminal to create a new state, named `BooksState`:
![Initial book list page](./images/bookstore-generate-state-books.png)
```bash
npx @ngxs/cli --name books --directory src/app/books
yarn ng generate ngxs-schematic:state books
```
* This command creates `books.state.ts` and `books.actions.ts` files in the `src/app/books/state` folder. See the [NGXS CLI documentation](https://www.ngxs.io/plugins/cli).
* This command creates several new files and updates `app.modules.ts` file to import the `NgxsModule` with the new state.
#### Get books data from backend
Create data types to map the data from the backend (you can check Swagger UI or your backend API to see the data format).
Import the `BooksState` to the `app.module.ts` in the `src/app` folder and then add the `BooksState` to `forRoot` static method of `NgxsModule` as an array element of the first parameter of the method.
![BookDto properties](./images/bookstore-swagger-book-dto-properties.png)
Open the `books.ts` file in the `app\store\models` folder and replace the content as below:
```js
// ...
import { BooksState } from './books/state/books.state'; //<== imported BooksState ==>
export namespace Books {
export interface State {
books: Response;
}
@NgModule({
imports: [
// other imports
export interface Response {
items: Book[];
totalCount: number;
}
NgxsModule.forRoot([BooksState]), //<== added BooksState ==>
export interface Book {
name: string;
type: BookType;
publishDate: string;
price: number;
lastModificationTime: string;
lastModifierId: string;
creationTime: string;
creatorId: string;
id: string;
}
//other imports
],
// ...
})
export class AppModule {}
export enum BookType {
Undefined,
Adventure,
Biography,
Dystopia,
Fantastic,
Horror,
Science,
ScienceFiction,
Poetry,
}
}
```
#### Generate proxies
* Added `Book` interface that represents a book object and `BookType` enum which represents a book category.
ABP CLI provides `generate-proxy` command that generates client proxies for your HTTP APIs to make easy to consume your services from the client side. Before running generate-proxy command, your host must be up and running. See the [CLI documentation](../CLI.md)
#### BooksService
Run the following command in the `angular` folder:
Create a new service, named `BooksService` to perform `HTTP` calls to the server:
```bash
abp generate-proxy --module app
yarn ng generate service books/shared/books
```
![Generate proxy command](./images/generate-proxy-command.png)
![service-terminal-output](./images/bookstore-service-terminal-output.png)
The generated files looks like below:
Open the `books.service.ts` file in `app\books\shared` folder and replace the content as below:
![Generated files](./images/generated-proxies.png)
```js
import { Injectable } from '@angular/core';
import { RestService } from '@abp/ng.core';
import { Books } from '../../store/models';
import { Observable } from 'rxjs';
#### GetBooks Action
@Injectable({
providedIn: 'root',
})
export class BooksService {
constructor(private restService: RestService) {}
get(): Observable<Books.Response> {
return this.restService.request<void, Books.Response>({
method: 'GET',
url: '/api/app/book'
});
}
}
```
Actions can either be thought of as a command which should trigger something to happen, or as the resulting event of something that has already happened. [See NGXS Actions documentation](https://www.ngxs.io/concepts/actions).
* We added the `get` method to get the list of books by performing an HTTP request to the related endpoint.
Open the `books.actions.ts` file in `app/books/state` folder and replace the content below:
Open the`books.actions.ts` file in `app\store\actions` folder and replace the content below:
```js
export class GetBooks {
@ -929,49 +974,43 @@ export class GetBooks {
#### Implement BooksState
Open the `books.state.ts` file in `app/books/state` folder and replace the content below:
Open the `books.state.ts` file in `app\store\states` folder and replace the content below:
```js
import { PagedResultDto } from '@abp/ng.core';
import { State, Action, StateContext, Selector } from '@ngxs/store';
import { GetBooks } from './books.actions';
import { BookService } from '../../app/shared/services';
import { GetBooks } from '../actions/books.actions';
import { Books } from '../models/books';
import { BooksService } from '../../books/shared/books.service';
import { tap } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { BookDto } from '../../app/shared/models';
export class BooksStateModel {
public book: PagedResultDto<BookDto>;
}
@State<BooksStateModel>({
@State<Books.State>({
name: 'BooksState',
defaults: { book: {} } as BooksStateModel,
defaults: { books: {} } as Books.State,
})
@Injectable()
export class BooksState {
@Selector()
static getBooks(state: BooksStateModel) {
return state.book.items || [];
static getBooks(state: Books.State) {
return state.books.items || [];
}
constructor(private bookService: BookService) {}
constructor(private booksService: BooksService) {}
@Action(GetBooks)
get(ctx: StateContext<BooksStateModel>) {
return this.bookService.getListByInput().pipe(
tap((booksResponse) => {
get(ctx: StateContext<Books.State>) {
return this.booksService.get().pipe(
tap(booksResponse => {
ctx.patchState({
book: booksResponse,
books: booksResponse,
});
})
}),
);
}
}
```
* We added the book property to BooksStateModel model.
* We added `@Injectable()` decorator to BookState class (Regquired for Ivy to work properly).
* We added the `GetBooks` action that retrieves the books data via `BooksService` that generated via ABP CLI and patches the state.
* We added the `GetBooks` action that retrieves the books data via `BooksService` and patches the state.
* `NGXS` requires to return the observable without subscribing it in the get function.
#### BookListComponent
@ -980,12 +1019,11 @@ Open the `book-list.component.ts` file in `app\books\book-list` folder and repla
```js
import { Component, OnInit } from '@angular/core';
import { Select, Store } from '@ngxs/store';
import { Store, Select } from '@ngxs/store';
import { BooksState } from '../../store/states';
import { Observable } from 'rxjs';
import { finalize } from 'rxjs/operators';
import { BookDto, BookType } from '../../app/shared/models';
import { GetBooks } from '../state/books.actions';
import { BooksState } from '../state/books.state';
import { Books } from '../../store/models';
import { GetBooks } from '../../store/actions';
@Component({
selector: 'app-book-list',
@ -994,13 +1032,13 @@ import { BooksState } from '../state/books.state';
})
export class BookListComponent implements OnInit {
@Select(BooksState.getBooks)
books$: Observable<BookDto[]>;
books$: Observable<Books.Book[]>;
booksType = BookType;
booksType = Books.BookType;
loading = false;
constructor(private store: Store) {}
constructor(private store: Store) { }
ngOnInit() {
this.get();
@ -1008,10 +1046,9 @@ export class BookListComponent implements OnInit {
get() {
this.loading = true;
this.store
.dispatch(new GetBooks())
.pipe(finalize(() => (this.loading = false)))
.subscribe(() => {});
this.store.dispatch(new GetBooks()).subscribe(() => {
this.loading = false;
});
}
}
```

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

@ -456,76 +456,161 @@ Run the application and try to delete a book.
In this section, you will learn how to create a new modal dialog form to create a new book.
#### Type definition
Open `books.ts` file in `app\store\models` folder and replace the content as below:
```js
export namespace Books {
export interface State {
books: Response;
}
export interface Response {
items: Book[];
totalCount: number;
}
export interface Book {
name: string;
type: BookType;
publishDate: string;
price: number;
lastModificationTime: string;
lastModifierId: string;
creationTime: string;
creatorId: string;
id: string;
}
export enum BookType {
Undefined,
Adventure,
Biography,
Dystopia,
Fantastic,
Horror,
Science,
ScienceFiction,
Poetry,
}
//<== added CreateUpdateBookInput interface ==>
export interface CreateUpdateBookInput {
name: string;
type: BookType;
publishDate: string;
price: number;
}
}
```
* We added `CreateUpdateBookInput` interface.
* You can see the properties of this interface from Swagger UI.
* The `CreateUpdateBookInput` interface matches with the `CreateUpdateBookDto` in the backend.
#### Service method
Open the `books.service.ts` file in `app\books\shared` folder and replace the content as below:
```js
import { Injectable } from '@angular/core';
import { RestService } from '@abp/ng.core';
import { Books } from '../../store/models';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class BooksService {
constructor(private restService: RestService) {}
get(): Observable<Books.Response> {
return this.restService.request<void, Books.Response>({
method: 'GET',
url: '/api/app/book'
});
}
//<== added create method ==>
create(createBookInput: Books.CreateUpdateBookInput): Observable<Books.Book> {
return this.restService.request<Books.CreateUpdateBookInput, Books.Book>({
method: 'POST',
url: '/api/app/book',
body: createBookInput
});
}
}
```
- We added the `create` method to perform an HTTP Post request to the server.
- `restService.request` function gets generic parameters for the types sent to and received from the server. This example sends a `CreateUpdateBookInput` object and receives a `Book` object (you can set `void` for request or return type if not used).
#### State definitions
Open `books.action.ts` in `books\state` folder and replace the content as below:
Open `books.action.ts` in `app\store\actions` folder and replace the content as below:
```js
import { CreateUpdateBookDto } from '../../app/shared/models'; //<== added this line ==>
import { Books } from '../models'; //<== added this line ==>
export class GetBooks {
static readonly type = '[Books] Get';
}
// added CreateUpdateBook class
//added CreateUpdateBook class
export class CreateUpdateBook {
static readonly type = '[Books] Create Update Book';
constructor(public payload: CreateUpdateBookDto) { }
constructor(public payload: Books.CreateUpdateBookInput) { }
}
```
* We imported the `CreateUpdateBookDto` model and created the `CreateUpdateBook` action.
* We imported the Books namespace and created the `CreateUpdateBook` action.
Open `books.state.ts` file in `books\state` folder and replace the content as below:
Open `books.state.ts` file in `app\store\states` and replace the content as below:
```js
import { PagedResultDto } from '@abp/ng.core';
import { State, Action, StateContext, Selector } from '@ngxs/store';
import { GetBooks, CreateUpdateBook } from './books.actions'; // <== added CreateUpdateBook==>
import { BookService } from '../../app/shared/services';
import { GetBooks, CreateUpdateBook } from '../actions/books.actions'; //<== added CreateUpdateBook==>
import { Books } from '../models/books';
import { BooksService } from '../../books/shared/books.service';
import { tap } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { BookDto } from '../../app/shared/models';
export class BooksStateModel {
public book: PagedResultDto<BookDto>;
}
@State<BooksStateModel>({
@State<Books.State>({
name: 'BooksState',
defaults: { book: {} } as BooksStateModel,
defaults: { books: {} } as Books.State,
})
@Injectable()
export class BooksState {
@Selector()
static getBooks(state: BooksStateModel) {
return state.book.items || [];
static getBooks(state: Books.State) {
return state.books.items || [];
}
constructor(private bookService: BookService) {}
constructor(private booksService: BooksService) { }
@Action(GetBooks)
get(ctx: StateContext<BooksStateModel>) {
return this.bookService.getListByInput().pipe(
tap((bookResponse) => {
get(ctx: StateContext<Books.State>) {
return this.booksService.get().pipe(
tap(booksResponse => {
ctx.patchState({
book: bookResponse,
books: booksResponse,
});
})
}),
);
}
// added CreateUpdateBook action listener
//added CreateUpdateBook action listener
@Action(CreateUpdateBook)
save(ctx: StateContext<BooksStateModel>, action: CreateUpdateBook) {
return this.bookService.createByInput(action.payload);
save(ctx: StateContext<Books.State>, action: CreateUpdateBook) {
return this.booksService.create(action.payload);
}
}
```
* We imported `CreateUpdateBook` action and defined the `save` method that will listen to a `CreateUpdateBook` action to create a book.
When the `SaveBook` action dispatched, the save method is being executed. It calls `createByInput` method of the `BookService`.
When the `SaveBook` action dispatched, the save method is being executed. It calls `create` method of the `BooksService`.
#### Add a modal to BookListComponent
@ -609,12 +694,11 @@ Open `book-list.component.ts` file in `books\book-list` folder and replace the c
```js
import { Component, OnInit } from '@angular/core';
import { Select, Store } from '@ngxs/store';
import { Store, Select } from '@ngxs/store';
import { BooksState } from '../../store/states';
import { Observable } from 'rxjs';
import { finalize } from 'rxjs/operators';
import { BookDto, BookType } from '../../app/shared/models';
import { GetBooks } from '../state/books.actions';
import { BooksState } from '../state/books.state';
import { Books } from '../../store/models';
import { GetBooks } from '../../store/actions';
@Component({
selector: 'app-book-list',
@ -623,15 +707,15 @@ import { BooksState } from '../state/books.state';
})
export class BookListComponent implements OnInit {
@Select(BooksState.getBooks)
books$: Observable<BookDto[]>;
books$: Observable<Books.Book[]>;
booksType = BookType;
booksType = Books.BookType;
loading = false;
isModalOpen = false; // <== added this line ==>
isModalOpen = false; //<== added this line ==>
constructor(private store: Store) {}
constructor(private store: Store) { }
ngOnInit() {
this.get();
@ -639,13 +723,12 @@ export class BookListComponent implements OnInit {
get() {
this.loading = true;
this.store
.dispatch(new GetBooks())
.pipe(finalize(() => (this.loading = false)))
.subscribe(() => {});
this.store.dispatch(new GetBooks()).subscribe(() => {
this.loading = false;
});
}
// added createBook method
//added createBook method
createBook() {
this.isModalOpen = true;
}
@ -666,13 +749,12 @@ Open `book-list.component.ts` file in `app\books\book-list` folder and replace t
```js
import { Component, OnInit } from '@angular/core';
import { Select, Store } from '@ngxs/store';
import { Store, Select } from '@ngxs/store';
import { BooksState } from '../../store/states';
import { Observable } from 'rxjs';
import { finalize } from 'rxjs/operators';
import { BookDto, BookType } from '../../app/shared/models';
import { GetBooks } from '../state/books.actions';
import { BooksState } from '../state/books.state';
import { FormGroup, FormBuilder, Validators } from '@angular/forms'; // <== added this line ==>
import { Books } from '../../store/models';
import { GetBooks } from '../../store/actions';
import { FormGroup, FormBuilder, Validators } from '@angular/forms'; //<== added this line ==>
@Component({
selector: 'app-book-list',
@ -681,17 +763,17 @@ import { FormGroup, FormBuilder, Validators } from '@angular/forms'; // <== adde
})
export class BookListComponent implements OnInit {
@Select(BooksState.getBooks)
books$: Observable<BookDto[]>;
books$: Observable<Books.Book[]>;
booksType = BookType;
booksType = Books.BookType;
loading = false;
isModalOpen = false;
form: FormGroup; // <== added this line ==>
form: FormGroup;
constructor(private store: Store, private fb: FormBuilder) {} // <== added FormBuilder ==>
constructor(private store: Store, private fb: FormBuilder) { } //<== added FormBuilder ==>
ngOnInit() {
this.get();
@ -699,10 +781,9 @@ export class BookListComponent implements OnInit {
get() {
this.loading = true;
this.store
.dispatch(new GetBooks())
.pipe(finalize(() => (this.loading = false)))
.subscribe(() => {});
this.store.dispatch(new GetBooks()).subscribe(() => {
this.loading = false;
});
}
createBook() {
@ -710,7 +791,7 @@ export class BookListComponent implements OnInit {
this.isModalOpen = true;
}
// added buildForm method
//added buildForm method
buildForm() {
this.form = this.fb.group({
name: ['', Validators.required],
@ -723,7 +804,6 @@ export class BookListComponent implements OnInit {
```
* We imported `FormGroup, FormBuilder and Validators`.
* We added `form: FormGroup` variable.
* We injected `fb: FormBuilder` service to the constructor. The [FormBuilder](https://angular.io/api/forms/FormBuilder) service provides convenient methods for generating controls. It reduces the amount of boilerplate needed to build complex forms.
* We added `buildForm` method to the end of the file and executed `buildForm()` in the `createBook` method. This method creates a reactive form to be able to create a new book.
* The `group` method of `FormBuilder`, `fb` creates a `FormGroup`.
@ -800,34 +880,35 @@ export class BooksModule { }
* We imported `NgbDatepickerModule` to be able to use the date picker.
Open `book-list.component.ts` file in `app\books\book-list` folder and replace the content as below:
```js
import { Component, OnInit } from '@angular/core';
import { Select, Store } from '@ngxs/store';
import { Store, Select } from '@ngxs/store';
import { BooksState } from '../../store/states';
import { Observable } from 'rxjs';
import { finalize } from 'rxjs/operators';
import { BookDto, BookType } from '../../app/shared/models';
import { GetBooks } from '../state/books.actions';
import { BooksState } from '../state/books.state';
import { Books } from '../../store/models';
import { GetBooks } from '../../store/actions';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; // <== added this line ==>
import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; //<== added this line ==>
@Component({
selector: 'app-book-list',
templateUrl: './book-list.component.html',
styleUrls: ['./book-list.component.scss'],
providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], // <== added this line ==>
providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }] //<== added this line ==>
})
export class BookListComponent implements OnInit {
@Select(BooksState.getBooks)
books$: Observable<BookDto[]>;
books$: Observable<Books.Book[]>;
booksType = BookType;
booksType = Books.BookType;
//added bookTypeArr array
bookTypeArr = Object.keys(BookType).filter(
(bookType) => typeof this.booksType[bookType] === 'number'
bookTypeArr = Object.keys(Books.BookType).filter(
bookType => typeof this.booksType[bookType] === 'number'
);
loading = false;
@ -836,7 +917,7 @@ export class BookListComponent implements OnInit {
form: FormGroup;
constructor(private store: Store, private fb: FormBuilder) {}
constructor(private store: Store, private fb: FormBuilder) { }
ngOnInit() {
this.get();
@ -844,10 +925,9 @@ export class BookListComponent implements OnInit {
get() {
this.loading = true;
this.store
.dispatch(new GetBooks())
.pipe(finalize(() => (this.loading = false)))
.subscribe(() => {});
this.store.dispatch(new GetBooks()).subscribe(() => {
this.loading = false;
});
}
createBook() {
@ -885,16 +965,35 @@ Now, you can open your browser to see the changes:
#### Saving the book
Open `book-list.component.html` in `app\books\book-list` folder and add the following `abp-button` to save the new book.
```html
<ng-template #abpFooter>
<button type="button" class="btn btn-secondary" #abpClose>
{%{{{ 'AbpAccount::Close' | abpLocalization }}}%}
</button>
<!--added save button-->
<button class="btn btn-primary" (click)="save()" [disabled]="form.invalid">
<i class="fa fa-check mr-1"></i>
{%{{{ 'AbpAccount::Save' | abpLocalization }}}%}
</button>
</ng-template>
```
* This adds a save button to the bottom area of the modal:
![Save button to the modal](./images/bookstore-new-book-form-v2.png)
Open `book-list.component.ts` file in `app\books\book-list` folder and replace the content as below:
```js
import { Component, OnInit } from '@angular/core';
import { Select, Store } from '@ngxs/store';
import { Store, Select } from '@ngxs/store';
import { BooksState } from '../../store/states';
import { Observable } from 'rxjs';
import { finalize } from 'rxjs/operators';
import { BookDto, BookType } from '../../app/shared/models';
import { GetBooks, CreateUpdateBook } from '../state/books.actions'; // <== added CreateUpdateBook ==>
import { BooksState } from '../state/books.state';
import { Books } from '../../store/models';
import { GetBooks, CreateUpdateBook } from '../../store/actions'; //<== added CreateUpdateBook ==>
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap';
@ -902,17 +1001,16 @@ import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap
selector: 'app-book-list',
templateUrl: './book-list.component.html',
styleUrls: ['./book-list.component.scss'],
providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }],
providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }]
})
export class BookListComponent implements OnInit {
@Select(BooksState.getBooks)
books$: Observable<BookDto[]>;
books$: Observable<Books.Book[]>;
booksType = BookType;
booksType = Books.BookType;
//added bookTypeArr array
bookTypeArr = Object.keys(BookType).filter(
(bookType) => typeof this.booksType[bookType] === 'number'
bookTypeArr = Object.keys(Books.BookType).filter(
bookType => typeof this.booksType[bookType] === 'number'
);
loading = false;
@ -921,7 +1019,7 @@ export class BookListComponent implements OnInit {
form: FormGroup;
constructor(private store: Store, private fb: FormBuilder) {}
constructor(private store: Store, private fb: FormBuilder) { }
ngOnInit() {
this.get();
@ -929,10 +1027,9 @@ export class BookListComponent implements OnInit {
get() {
this.loading = true;
this.store
.dispatch(new GetBooks())
.pipe(finalize(() => (this.loading = false)))
.subscribe(() => {});
this.store.dispatch(new GetBooks()).subscribe(() => {
this.loading = false;
});
}
createBook() {
@ -965,46 +1062,37 @@ export class BookListComponent implements OnInit {
```
* We imported `CreateUpdateBook`.
* We added `save` method
Open `book-list.component.html` in `app\books\book-list` folder and add the following `abp-button` to save the new book.
```html
<ng-template #abpFooter>
<button type="button" class="btn btn-secondary" #abpClose>
{%{{{ 'AbpAccount::Close' | abpLocalization }}}%}
</button>
<!--added save button-->
<button class="btn btn-primary" (click)="save()" [disabled]="form.invalid">
<i class="fa fa-check mr-1"></i>
{%{{{ 'AbpAccount::Save' | abpLocalization }}}%}
</button>
</ng-template>
```
Find the `<form [formGroup]="form">` tag and replace below content:
* We added `save` method
```html
<form [formGroup]="form" (ngSubmit)="save()"> <!-- added the ngSubmit -->
```
### Updating an existing book
* We added the `(ngSubmit)="save()"` to `<form>` element to save a new book by pressing the enter.
* We added `abp-button` to the bottom area of the modal to save a new book.
#### BooksService
The final modal UI looks like below:
Open the `books.service.ts` in `app\books\shared` folder and add the `getById` and `update` methods.
![Save button to the modal](./images/bookstore-new-book-form-v2.png)
```js
getById(id: string): Observable<Books.Book> {
return this.restService.request<void, Books.Book>({
method: 'GET',
url: `/api/app/book/${id}`
});
}
### Updating a book
update(updateBookInput: Books.CreateUpdateBookInput, id: string): Observable<Books.Book> {
return this.restService.request<Books.CreateUpdateBookInput, Books.Book>({
method: 'PUT',
url: `/api/app/book/${id}`,
body: updateBookInput
});
}
```
#### CreateUpdateBook action
Open the `books.actions.ts` in `books\state` folder and replace the content as below:
Open the `books.actions.ts` in `app\store\actions` folder and replace the content as below:
```js
import { CreateUpdateBookDto } from '../../app/shared/models';
import { Books } from '../models';
export class GetBooks {
static readonly type = '[Books] Get';
@ -1012,55 +1100,54 @@ export class GetBooks {
export class CreateUpdateBook {
static readonly type = '[Books] Create Update Book';
constructor(public payload: CreateUpdateBookDto, public id?: string) { } // <== added id parameter ==>
constructor(public payload: Books.CreateUpdateBookInput, public id?: string) { } //<== added id parameter ==>
}
```
* We added `id` parameter to the `CreateUpdateBook` action's constructor.
Open the `books.state.ts` in `books\state` folder and replace the `save` method as below:
Open the `books.state.ts` in `app\store\states` folder and replace the `save` method as below:
```js
@Action(CreateUpdateBook)
save(ctx: StateContext<BooksStateModel>, action: CreateUpdateBook) {
if (action.id) {
return this.bookService.updateByIdAndInput(action.payload, action.id);
} else {
return this.bookService.createByInput(action.payload);
}
save(ctx: StateContext<Books.State>, action: CreateUpdateBook) {
if (action.id) {
return this.booksService.update(action.payload, action.id);
} else {
return this.booksService.create(action.payload);
}
}
```
#### BookListComponent
Open `book-list.component.ts` in `app\books\book-list` folder and inject `BookService` dependency by adding it to the constructor and add a variable named `selectedBook`.
Open `book-list.component.ts` in `app\books\book-list` folder and inject `BooksService` dependency by adding it to the constructor and add a variable named `selectedBook`.
```js
import { Component, OnInit } from '@angular/core';
import { Select, Store } from '@ngxs/store';
import { Store, Select } from '@ngxs/store';
import { BooksState } from '../../store/states';
import { Observable } from 'rxjs';
import { finalize } from 'rxjs/operators';
import { BookDto, BookType } from '../../app/shared/models';
import { GetBooks, CreateUpdateBook } from '../state/books.actions';
import { BooksState } from '../state/books.state';
import { Books } from '../../store/models';
import { GetBooks, CreateUpdateBook } from '../../store/actions';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap';
import { BookService } from '../../app/shared/services'; // <== imported BookService ==>
import { BooksService } from '../shared/books.service'; //<== imported BooksService ==>
@Component({
selector: 'app-book-list',
templateUrl: './book-list.component.html',
styleUrls: ['./book-list.component.scss'],
providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }],
providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }]
})
export class BookListComponent implements OnInit {
@Select(BooksState.getBooks)
books$: Observable<BookDto[]>;
books$: Observable<Books.Book[]>;
booksType = BookType;
booksType = Books.BookType;
bookTypeArr = Object.keys(BookType).filter(
(bookType) => typeof this.booksType[bookType] === 'number'
bookTypeArr = Object.keys(Books.BookType).filter(
bookType => typeof this.booksType[bookType] === 'number'
);
loading = false;
@ -1069,9 +1156,9 @@ export class BookListComponent implements OnInit {
form: FormGroup;
selectedBook = {} as BookDto; // <== declared selectedBook ==>
selectedBook = {} as Books.Book; //<== declared selectedBook ==>
constructor(private store: Store, private fb: FormBuilder, private bookService: BookService) {} //<== injected BookService ==>
constructor(private store: Store, private fb: FormBuilder, private booksService: BooksService) { }
ngOnInit() {
this.get();
@ -1079,38 +1166,39 @@ export class BookListComponent implements OnInit {
get() {
this.loading = true;
this.store
.dispatch(new GetBooks())
.pipe(finalize(() => (this.loading = false)))
.subscribe(() => {});
this.store.dispatch(new GetBooks()).subscribe(() => {
this.loading = false;
});
}
// <== this method is replaced ==>
//<== this method is replaced ==>
createBook() {
this.selectedBook = {} as BookDto; // <== added ==>
this.selectedBook = {} as Books.Book; //<== added ==>
this.buildForm();
this.isModalOpen = true;
}
// <== added editBook method ==>
//<== added editBook method ==>
editBook(id: string) {
this.bookService.getById(id).subscribe((book) => {
this.booksService.getById(id).subscribe(book => {
this.selectedBook = book;
this.buildForm();
this.isModalOpen = true;
});
}
// <== this method is replaced ==>
//<== this method is replaced ==>
buildForm() {
this.form = this.fb.group({
name: [this.selectedBook.name || '', Validators.required],
name: [this.selectedBook.name || "", Validators.required],
type: [this.selectedBook.type || null, Validators.required],
publishDate: [
this.selectedBook.publishDate ? new Date(this.selectedBook.publishDate) : null,
Validators.required,
this.selectedBook.publishDate
? new Date(this.selectedBook.publishDate)
: null,
Validators.required
],
price: [this.selectedBook.price || null, Validators.required],
price: [this.selectedBook.price || null, Validators.required]
});
}
@ -1120,8 +1208,7 @@ export class BookListComponent implements OnInit {
}
//<== added this.selectedBook.id ==>
this.store
.dispatch(new CreateUpdateBook(this.form.value, this.selectedBook.id))
this.store.dispatch(new CreateUpdateBook(this.form.value, this.selectedBook.id))
.subscribe(() => {
this.isModalOpen = false;
this.form.reset();
@ -1131,9 +1218,9 @@ export class BookListComponent implements OnInit {
}
```
* We imported `BookService`.
* We declared a variable named `selectedBook` as `BookDto`.
* We injected `BookService` to the constructor. `BookService` is being used to retrieve the book data which is being edited.
* We imported `BooksService`.
* We declared a variable named `selectedBook` as `Books.Book`.
* We injected `BooksService` to the constructor. `BooksService` is being used to retrieve the book data which is being edited.
* We added `editBook` method. This method fetches the book with the given `Id` and sets it to `selectedBook` object.
* We replaced the `buildForm` method so that it creates the form with the `selectedBook` data.
* We replaced the `createBook` method so it sets `selectedBook` to an empty object.
@ -1211,9 +1298,24 @@ Open `book-list.component.html` in `app\books\book-list` folder and find the `<n
### Deleting a book
#### BooksService
Open `books.service.ts` in `app\books\shared` folder and add the below `delete` method to delete a book.
```js
delete(id: string): Observable<void> {
return this.restService.request<void, void>({
method: 'DELETE',
url: `/api/app/book/${id}`
});
}
```
* `Delete` method gets `id` parameter and makes a `DELETE` HTTP request to the relevant endpoint.
#### DeleteBook action
Open `books.actions.ts` in `books\state `folder and add an action named `DeleteBook`.
Open `books.actions.ts` in `app\store\actions `folder and add an action named `DeleteBook`.
```js
export class DeleteBook {
@ -1222,58 +1324,53 @@ export class DeleteBook {
}
```
Open the `books.state.ts` in `books\state` folder and replace the content as below:
Open the `books.state.ts` in `app\store\states` folder and replace the content as below:
```js
import { PagedResultDto } from '@abp/ng.core';
import { State, Action, StateContext, Selector } from '@ngxs/store';
import { GetBooks, CreateUpdateBook, DeleteBook } from './books.actions'; // <== added DeleteBook==>
import { BookService } from '../../app/shared/services';
import { GetBooks, CreateUpdateBook, DeleteBook } from '../actions/books.actions'; //<== added DeleteBook==>
import { Books } from '../models/books';
import { BooksService } from '../../books/shared/books.service';
import { tap } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { BookDto } from '../../app/shared/models';
export class BooksStateModel {
public book: PagedResultDto<BookDto>;
}
@State<BooksStateModel>({
@State<Books.State>({
name: 'BooksState',
defaults: { book: {} } as BooksStateModel,
defaults: { books: {} } as Books.State,
})
@Injectable()
export class BooksState {
@Selector()
static getBooks(state: BooksStateModel) {
return state.book.items || [];
static getBooks(state: Books.State) {
return state.books.items || [];
}
constructor(private bookService: BookService) {}
constructor(private booksService: BooksService) { }
@Action(GetBooks)
get(ctx: StateContext<BooksStateModel>) {
return this.bookService.getListByInput().pipe(
tap((booksResponse) => {
get(ctx: StateContext<Books.State>) {
return this.booksService.get().pipe(
tap(booksResponse => {
ctx.patchState({
book: booksResponse,
books: booksResponse,
});
})
}),
);
}
@Action(CreateUpdateBook)
save(ctx: StateContext<BooksStateModel>, action: CreateUpdateBook) {
save(ctx: StateContext<Books.State>, action: CreateUpdateBook) {
if (action.id) {
return this.bookService.updateByIdAndInput(action.payload, action.id);
return this.booksService.update(action.payload, action.id);
} else {
return this.bookService.createByInput(action.payload);
return this.booksService.create(action.payload);
}
}
// <== added DeleteBook action listener ==>
//<== added DeleteBook ==>
@Action(DeleteBook)
delete(ctx: StateContext<BooksStateModel>, action: DeleteBook) {
return this.bookService.deleteById(action.id);
delete(ctx: StateContext<Books.State>, action: DeleteBook) {
return this.booksService.delete(action.id);
}
}
```
@ -1282,8 +1379,27 @@ export class BooksState {
- We added `DeleteBook` action listener to the end of the file.
#### Add a delete button
Open `book-list.component.html` in `app\books\book-list` folder and modify the `ngbDropdownMenu` to add the delete button as shown below:
```html
<div ngbDropdownMenu>
<!-- added Delete button -->
<button ngbDropdownItem (click)="delete(data.id, data.name)">
{%{{{ 'AbpAccount::Delete' | abpLocalization }}}%}
</button>
</div>
```
The final actions dropdown UI looks like below:
#### Delete confirmation popup
![bookstore-final-actions-dropdown](./images/bookstore-final-actions-dropdown.png)
#### Delete confirmation dialog
Open `book-list.component.ts` in`app\books\book-list` folder and inject the `ConfirmationService`.
@ -1294,29 +1410,26 @@ import { ConfirmationService } from '@abp/ng.theme.shared';
//...
constructor(
private store: Store,
private fb: FormBuilder,
private bookService: BookService,
private confirmation: ConfirmationService // <== added this line ==>
private store: Store, private fb: FormBuilder,
private booksService: BooksService,
private confirmationService: ConfirmationService // <== added this line ==>
) { }
```
* We imported `ConfirmationService`.
* We injected `ConfirmationService` to the constructor.
See the [Confirmation Popup documentation](https://docs.abp.io/en/abp/latest/UI/Angular/Confirmation-Service)
In the `book-list.component.ts` add a delete method :
```js
import { GetBooks, CreateUpdateBook, DeleteBook } from '../state/books.actions' ;// <== imported DeleteBook ==>
import { GetBooks, CreateUpdateBook, DeleteBook } from '../../store/actions'; //<== added DeleteBook ==>
import { ConfirmationService, Confirmation } from '@abp/ng.theme.shared'; //<== imported Confirmation ==>
import { ConfirmationService, Confirmation } from '@abp/ng.theme.shared'; //<== added Confirmation ==>
//...
delete(id: string) {
this.confirmation
delete(id: string, name: string) {
this.confirmationService
.warn('::AreYouSureToDelete', 'AbpAccount::AreYouSure')
.subscribe(status => {
if (status === Confirmation.Status.confirm) {
@ -1326,30 +1439,10 @@ delete(id: string) {
}
```
The `delete` method shows a confirmation popup and subscribes for the user response. `DeleteBook` action dispatched only if user clicks to the `Yes` button. The confirmation popup looks like below:
![bookstore-confirmation-popup](./images/bookstore-confirmation-popup.png)
#### Add a delete button
Open `book-list.component.html` in `app\books\book-list` folder and modify the `ngbDropdownMenu` to add the delete button as shown below:
```html
<div ngbDropdownMenu>
<!-- added Delete button -->
<button ngbDropdownItem (click)="delete(data.id)">
{%{{{ 'AbpAccount::Delete' | abpLocalization }}}%}
</button>
</div>
```
The final actions dropdown UI looks like below:
![bookstore-final-actions-dropdown](./images/bookstore-final-actions-dropdown.png)
{{end}}
### Next Part

4
docs/en/Tutorials/Part-3.md

@ -96,9 +96,9 @@ namespace Acme.BookStore
````
* `IRepository<Book, Guid>` is injected and used it in the `SeedAsync` to create two book entities as the test data.
* `IGuidGenerator` is injected to create GUIDs. While `Guid.NewGuid()` would perfectly work for testing, `IGuidGenerator` has additional features especially important while using real databases. Further information, see the [Guid generation document](../Guid-Generation.md).
### Testing the application service BookAppService
### Testing the application service BookAppService
* `IGuidGenerator` is injected to create GUIDs. While `Guid.NewGuid()` would perfectly work for testing, `IGuidGenerator` has additional features especially important while using real databases. Further information, see the [Guid generation document](https://docs.abp.io/{{Document_Language_Code}}/abp/{{Document_Version}}/Guid-Generation).
Create a test class named `BookAppService_Tests` in the `Acme.BookStore.Application.Tests` project:

BIN
docs/en/Tutorials/images/bookstore-angular-file-tree.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 241 KiB

BIN
docs/en/Tutorials/images/generate-proxy-command.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

BIN
docs/en/Tutorials/images/generated-proxies.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

12
docs/en/UI/Angular/Config-State.md

@ -236,7 +236,7 @@ const newRoute: ABP.Route = {
path: "page",
invisible: false,
order: 2,
requiredPolicy: "MyProjectName.MyNewPage"
requiredPolicy: "MyProjectName::MyNewPage"
};
this.config.dispatchAddRoute(newRoute);
@ -248,25 +248,23 @@ The `newRoute` will be placed as at root level, i.e. without any parent routes a
If you want **to add a child route, you can do this:**
```js
import { eIdentityRouteNames } from '@abp/ng.identity';
// this.config is instance of ConfigStateService
const newRoute: ABP.Route = {
parentName: eIdentityRouteNames.IdentityManagement,
parentName: "AbpAccount::Login",
name: "My New Page",
iconClass: "fa fa-dashboard",
path: "page",
invisible: false,
order: 2,
requiredPolicy: "MyProjectName.MyNewPage"
requiredPolicy: "MyProjectName::MyNewPage"
};
this.config.dispatchAddRoute(newRoute);
// returns a state stream which emits after dispatch action is complete
```
The `newRoute` will then be placed as a child of the parent route named `eIdentityRouteNames.IdentityManagement` and its url will be set as `'/identity/page'`.
The `newRoute` will then be placed as a child of the parent route named `'AbpAccount::Login'` and its url will be set as `'/account/login/page'`.
#### Route Configuration Properties
@ -293,4 +291,4 @@ Please refer to `Config.Environment` type for all the properties you can pass to
## What's Next?
- [Modifying the Menu](./Modifying-the-Menu.md)
* [Component Replacement](./Component-Replacement.md)

185
docs/en/UI/Angular/Confirmation-Service.md

@ -1,185 +0,0 @@
# Confirmation Popup
You can use the `ConfirmationService` in @abp/ng.theme.shared package to display a confirmation popup by placing at the root level in your project.
## Getting Started
You do not have to provide the `ConfirmationService` at module or component level, because it is already **provided in root**. You can inject and start using it immediately in your components, directives, or services.
```js
import { ConfirmationService } from '@abp/ng.theme.shared';
@Component({
/* class metadata here */
})
class DemoComponent {
constructor(private confirmation: ConfirmationService) {}
}
```
## Usage
You can use the `success`, `warn`, `error`, and `info` methods of `ConfirmationService` to display a confirmation popup.
### How to Display a Confirmation Popup
```js
const confirmationStatus$ = this.confirmation.success('Message', 'Title');
```
- The `ConfirmationService` methods accept three parameters that are `message`, `title`, and `options`.
- `success`, `warn`, `error`, and `info` methods return an [RxJS Subject](https://rxjs-dev.firebaseapp.com/guide/subject) to listen to confirmation popup closing event. The type of event value is [`Confirmation.Status`](https://github.com/abpframework/abp/blob/master/npm/ng-packs/packages/theme-shared/src/lib/models/confirmation.ts#L24) that is an enum.
### How to Listen Closing Event
You can subscribe to the confirmation closing event like below:
```js
import { Confirmation, ConfirmationService } from '@abp/ng.theme.shared';
constructor(private confirmation: ConfirmationService) {}
this.confirmation
.warn('::WillBeDeleted', { key: '::AreYouSure', defaultValue: 'Are you sure?' })
.subscribe((status: Confirmation.Status) => {
// your code here
});
```
- The `message` and `title` parameters accept a string, localization key or localization object. See the [localization document](./Localization.md)
- `Confirmation.Status` is an enum and has three properties;
- `Confirmation.Status.confirm` is a closing event value that will be emitted when the popup is closed by the confirm button.
- `Confirmation.Status.reject` is a closing event value that will be emitted when the popup is closed by the cancel button.
- `Confirmation.Status.dismiss` is a closing event value that will be emitted when the popup is closed by pressing the escape.
If you are not interested in the confirmation status, you do not have to subscribe to the returned observable:
```js
this.confirmation.error('You are not authorized.', 'Error');
```
### How to Display a Confirmation Popup With Given Options
Options can be passed as the third parameter to `success`, `warn`, `error`, and `info` methods:
```js
const options: Partial<Confirmation.Options> = {
hideCancelBtn: false,
hideYesBtn: false,
cancelText: 'Close',
yesText: 'Confirm',
messageLocalizationParams: ['Demo'],
titleLocalizationParams: [],
};
this.confirmation.warn(
'AbpIdentity::RoleDeletionConfirmationMessage',
'Are you sure?',
options,
);
```
- `hideCancelBtn` option hides the cancellation button when `true`. Default value is `false`
- `hideYesBtn` option hides the confirmation button when `true`. Default value is `false`
- `cancelText` is the text of the cancellation button. A localization key or localization object can be passed. Default value is `AbpUi::Cancel`
- `yesText` is the text of the confirmation button. A localization key or localization object can be passed. Default value is `AbpUi::Yes`
- `messageLocalizationParams` is the interpolation parameters for the localization of the message.
- `titleLocalizationParams` is the interpolation parameters for the localization of the title.
With the options above, the confirmation popup looks like this:
![confirmation](./images/confirmation.png)
You are able to pass in an HTML string as title, message, or button texts. Here is an example:
```js
const options: Partial<Confirmation.Options> = {
yesText: '<i class="fa fa-trash mr-1"></i>Yes, delete it',
};
this.confirmation.warn(
`
<strong>Role Demo</strong> will be <strong>deleted</strong>
<br>
Do you confirm that?
`,
'<span class="my-custom-title">Are you sure?</span>',
options,
);
```
Since the values are HTML now, localization should be handled manually. Check out the [LocalizationService](./Localization#using-the-localization-service) to see how you can accomplish that.
> Please note that all strings will be sanitized by Angular and not every HTML string will work. Only values that are considered as "safe" by Angular will be displayed.
### How to Remove a Confirmation Popup
The open confirmation popup can be removed manually via the `clear` method:
```js
this.confirmation.clear();
```
## API
### success
```js
success(
message: Config.LocalizationParam,
title: Config.LocalizationParam,
options?: Partial<Confirmation.Options>,
): Observable<Confirmation.Status>
```
> See the [`Config.LocalizationParam` type](https://github.com/abpframework/abp/blob/master/npm/ng-packs/packages/core/src/lib/models/config.ts#L46) and [`Confirmation` namespace](https://github.com/abpframework/abp/blob/master/npm/ng-packs/packages/theme-shared/src/lib/models/confirmation.ts)
### warn
```js
warn(
message: Config.LocalizationParam,
title: Config.LocalizationParam,
options?: Partial<Confirmation.Options>,
): Observable<Confirmation.Status>
```
### error
```js
error(
message: Config.LocalizationParam,
title: Config.LocalizationParam,
options?: Partial<Confirmation.Options>,
): Observable<Confirmation.Status>
```
### info
```js
info(
message: Config.LocalizationParam,
title: Config.LocalizationParam,
options?: Partial<Confirmation.Options>,
): Observable<Confirmation.Status>
```
### clear
```js
clear(
status: Confirmation.Status = Confirmation.Status.dismiss
): void
```
- `status` parameter is the value of the confirmation closing event.
## What's Next?
- [Toast Overlay](./Toaster-Service.md)

2
docs/en/UI/Angular/Http-Requests.md

@ -206,4 +206,4 @@ You may find `Rest.Observe` enum [here](https://github.com/abpframework/abp/blob
## What's Next?
* [Localization](./Localization.md)
* [Localization](./Localization.md)

199
docs/en/UI/Angular/Modifying-the-Menu.md

@ -1,199 +0,0 @@
# Modifying the Menu
The menu is inside the `ApplicationLayoutComponent` in the @abp/ng.theme.basic package. There are several methods for modifying the menu elements. This document covers these methods. If you would like to replace the menu completely, please refer to [Component Replacement documentation](./Component-Replacement.md) and learn how to replace a layout.
<!-- TODO: Replace layout replacement document with component replacement. Layout replacement document will be created.-->
## How to Add a Logo
The `logoUrl` property in the environment variables is the url of the logo.
You can add your logo to `src/assets` folder and set the `logoUrl` as shown below:
```js
export const environment = {
// other configurations
application: {
name: 'MyProjectName',
logoUrl: 'assets/logo.png',
},
// other configurations
};
```
## How to Add a Navigation Element
### Via `routes` Property in `AppRoutingModule`
You can define your routes by adding `routes` as a child property to `data` property of a route configuration in the `app-routing.module`. The `@abp/ng.core` package organizes your routes and stores them in the `ConfigState`. `ApplicationLayoutComponent` gets routes from store and displays them on the menu.
You can add the `routes` property like below:
```js
{
path: 'your-path',
data: {
routes: {
name: 'Your navigation',
order: 3,
iconClass: 'fas fa-question-circle',
requiredPolicy: 'permission key here',
children: [
{
path: 'child',
name: 'Your child navigation',
order: 1,
requiredPolicy: 'permission key here',
},
],
} as ABP.Route, // can be imported from @abp/ng.core
}
}
```
- `name` is the label of the navigation element. A localization key or a localization object can be passed.
- `order` is the order of the navigation element.
- `iconClass` is the class of the `i` tag, which is placed to the left of the navigation label.
- `requiredPolicy` is the permission key to access the page. See the [Permission Management document](./Permission-Management.md)
- `children` is an array and is used for declaring child navigation elements. The child navigation element will be placed as a child route which will be available at `'/your-path/child'` based on the given `path` property.
After adding the `routes` property as described above, the navigation menu looks like this:
![navigation-menu-via-app-routing](./images/navigation-menu-via-app-routing.png)
## Via ConfigState
The `dispatchAddRoute` method of `ConfigStateService` adds a new navigation element to the menu.
```js
// this.config is instance of ConfigStateService
const newRoute: ABP.Route = {
name: 'My New Page',
iconClass: 'fa fa-dashboard',
path: 'page',
invisible: false,
order: 2,
requiredPolicy: 'MyProjectName.MyNewPage',
} as Omit<ABP.Route, 'children'>;
this.config.dispatchAddRoute(newRoute);
// returns a state stream which emits after dispatch action is complete
```
The `newRoute` will be placed as at root level, i.e. without any parent routes, and its url will be stored as `'/path'`.
If you want **to add a child route, you can do this:**
```js
// this.config is instance of ConfigStateService
// eIdentityRouteNames enum can be imported from @abp/ng.identity
const newRoute: ABP.Route = {
parentName: eIdentityRouteNames.IdentityManagement,
name: 'My New Page',
iconClass: 'fa fa-dashboard',
path: 'page',
invisible: false,
order: 3,
requiredPolicy: 'MyProjectName.MyNewPage'
} as Omit<ABP.Route, 'children'>;
this.config.dispatchAddRoute(newRoute);
// returns a state stream which emits after dispatch action is complete
```
The `newRoute` will then be placed as a child of the parent route named `eIdentityRouteNames.IdentityManagement` and its url will be set as `'/identity/page'`.
The new route will be added like below:
![navigation-menu-via-config-state](./images/navigation-menu-via-config-state.png)
## How to Patch a Navigation Element
The `dispatchPatchRouteByName` method finds a route by its name and replaces its configuration in the store with the new configuration passed as the second parameter.
```js
// this.config is instance of ConfigStateService
// eIdentityRouteNames enum can be imported from @abp/ng.identity
const newRouteConfig: Partial<ABP.Route> = {
iconClass: 'fas fa-home',
parentName: eIdentityRouteNames.Administration,
order: 0,
children: [
{
name: 'Dashboard',
path: 'dashboard',
},
],
};
this.config.dispatchPatchRouteByName('::Menu:Home', newRouteConfig);
// returns a state stream which emits after dispatch action is complete
```
* Moved the _Home_ navigation under the _Administration_ dropdown based on given `parentName`.
* Added an icon.
* Specified the order.
* Added a child route named _Dashboard_.
After the patch above, navigation elements looks like below:
![navigation-menu-after-patching](./images/navigation-menu-after-patching.png)
## How to Add an Element to Right Part of the Menu
The right part elements are stored in the `LayoutState` that is in the @abp/ng.theme.basic package.
The `dispatchAddNavigationElement` method of the `LayoutStateService` adds an element to the right part of the menu.
You can insert an element by adding your template to `app.component` and calling the `dispatchAddNavigationElement` method:
```js
import { Layout, LayoutStateService } from '@abp/ng.theme.basic'; // added this line
@Component({
selector: 'app-root',
template: `
<!-- Added below content -->
<ng-template #search
><input type="search" placeholder="Search" class="bg-transparent border-0"
/></ng-template>
`,
})
export class AppComponent {
// Added ViewChild
@ViewChild('search', { static: false, read: TemplateRef }) searchElementRef: TemplateRef<any>;
constructor(private layout: LayoutStateService) {} // injected LayoutStateService
// Added ngAfterViewInit
ngAfterViewInit() {
const newElement = {
name: 'Search',
element: this.searchElementRef,
order: 1,
} as Layout.NavigationElement;
this.layout.dispatchAddNavigationElement(newElement);
}
}
```
This inserts a search input to the menu. The final UI looks like below:
![navigation-menu-search-input](./images/navigation-menu-search-input.png)
## How to Remove an Element From Right Part of the Menu
TODO
## What's Next
* [Component Replacement](./Component-Replacement.md)

2
docs/en/UI/Angular/Permission-Management.md

@ -76,4 +76,4 @@ Granted Policies are stored in the `auth` property of `ConfigState`.
## What's Next?
- [Confirmation Popup](./Confirmation-Service.md)
* [Config State](./Config-State.md)

158
docs/en/UI/Angular/Toaster-Service.md

@ -1,158 +0,0 @@
# Toast Overlay
You can use the `ToasterService` in @abp/ng.theme.shared package to display messages in an overlay by placing at the root level in your project.
## Getting Started
You do not have to provide the `ToasterService` at module or component level, because it is already **provided in root**. You can inject and start using it immediately in your components, directives, or services.
```js
import { ToasterService } from '@abp/ng.theme.shared';
@Component({
/* class metadata here */
})
class DemoComponent {
constructor(private toaster: ToasterService) {}
}
```
## Usage
You can use the `success`, `warn`, `error`, and `info` methods of `ToasterService` to display an overlay.
### How to Display a Toast Overlay
```js
this.toast.success('Message', 'Title');
```
- The `ToasterService` methods accept three parameters that are `message`, `title`, and `options`.
- `success`, `warn`, `error`, and `info` methods return the id of opened toast overlay. The toast can be removed with this id.
### How to Display a Toast Overlay With Given Options
Options can be passed as the third parameter to `success`, `warn`, `error`, and `info` methods:
```js
import { Toaster, ToasterService } from '@abp/ng.theme.shared';
//...
constructor(private toaster: ToasterService) {}
//...
const options: Partial<Toaster.ToastOptions> = {
life: 10000,
sticky: false,
closable: true,
tapToDismiss: true,
messageLocalizationParams: ['Demo', '1'],
titleLocalizationParams: []
};
this.toaster.error('AbpUi::EntityNotFoundErrorMessage', 'AbpUi::Error', options);
```
- `life` option is the closing time in milliseconds. Default value is `5000`.
- `sticky` option keeps toast overlay on the screen by ignoring the `life` option when `true`. Default value is `false`.
- `closable` option displays the close icon on the toast overlay when it is `true`. Default value is `true`.
- `tapToDismiss` option, when `true`, allows closing the toast overlay by clicking over it. Default value is `false`.
- `yesText` is the text of the confirmation button. A localization key or localization object can be passed. Default value is `AbpUi::Yes`.
- `messageLocalizationParams` is the interpolation parameters for the localization of the message.
- `titleLocalizationParams` is the interpolation parameters for the localization of the title.
With the options above, the toast overlay looks like this:
![toast](./images/toast.png)
### How to Remove a Toast Overlay
The open toast overlay can be removed manually via the `remove` method by passing the `id` of toast:
```js
const toastId = this.toast.success('Message', 'Title')
this.toast.remove(toastId);
```
### How to Remove All Toasts
The all open toasts can be removed manually via the `clear` method:
```js
this.toast.clear();
```
## API
### success
```js
success(
message: Config.LocalizationParam,
title: Config.LocalizationParam,
options?: Partial<Toaster.ToastOptions>,
): number
```
- `Config` namespace can be imported from `@abp/ng.core`.
- `Toaster` namespace can be imported from `@abp/ng.theme.shared`.
> See the [`Config.LocalizationParam` type](https://github.com/abpframework/abp/blob/master/npm/ng-packs/packages/core/src/lib/models/config.ts#L46) and [`Toaster` namespace](https://github.com/abpframework/abp/blob/master/npm/ng-packs/packages/theme-shared/src/lib/models/toaster.ts)
### warn
```js
warn(
message: Config.LocalizationParam,
title: Config.LocalizationParam,
options?: Partial<Toaster.ToastOptions>,
): number
```
### error
```js
error(
message: Config.LocalizationParam,
title: Config.LocalizationParam,
options?: Partial<Toaster.ToastOptions>,
): number
```
### info
```js
info(
message: Config.LocalizationParam,
title: Config.LocalizationParam,
options?: Partial<Toaster.ToastOptions>,
): number
```
### remove
```js
remove(id: number): void
```
Removes an open toast by the given id.
### clear
```js
clear(): void
```
Removes all open toasts.
## See Also
- [Confirmation Popup](./Confirmation-Service.md)
## What's Next?
- [Config State](./Config-State.md)

BIN
docs/en/UI/Angular/images/confirmation.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

BIN
docs/en/UI/Angular/images/navigation-menu-after-patching.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

BIN
docs/en/UI/Angular/images/navigation-menu-search-input.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

BIN
docs/en/UI/Angular/images/navigation-menu-via-app-routing.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

BIN
docs/en/UI/Angular/images/navigation-menu-via-config-state.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

BIN
docs/en/UI/Angular/images/toast.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

22
docs/en/UI/Common/Utils/Linked-List.md

@ -1270,7 +1270,7 @@ find(predicate: ListIteratorFn<T>): ListNode<T> | undefined
Finds the first node from the list that matches the given predicate:
```js
list.addManyTail(['a', 'b', 'b', 'c']);
list.addTailMany(['a', 'b', 'b', 'c']);
// "a" <-> "b" <-> "b" <-> "c"
@ -1294,7 +1294,7 @@ findIndex(predicate: ListIteratorFn<T>): number
Finds the position of the first node from the list that matches the given predicate:
```js
list.addManyTail(['a', 'b', 'b', 'c']);
list.addTailMany(['a', 'b', 'b', 'c']);
// "a" <-> "b" <-> "b" <-> "c"
@ -1322,7 +1322,7 @@ get(position: number): ListNode<T> | undefined
Finds and returns the node with specific position in the list:
```js
list.addManyTail(['a', 'b', 'c']);
list.addTailMany(['a', 'b', 'c']);
// "a" <-> "b" <-> "c"
@ -1346,7 +1346,7 @@ indexOf(value: T, compareFn?: ListComparisonFn<T>): number
Finds the position of the first node from the list that has the given value:
```js
list.addManyTail(['a', 'b', 'b', 'c']);
list.addTailMany(['a', 'b', 'b', 'c']);
// "a" <-> "b" <-> "b" <-> "c"
@ -1368,7 +1368,7 @@ i3 === -1
You may pass a custom compare function to detect the searched value:
```js
list.addManyTail([{ x: 1 }, { x: 0 }, { x: 2 }, { x: 0 }, { x: 3 }]);
list.addTailMany([{ x: 1 }, { x: 0 }, { x: 2 }, { x: 0 }, { x: 3 }]);
// {"x":1} <-> {"x":0} <-> {"x":2} <-> {"x":0} <-> {"x":3}
@ -1408,7 +1408,7 @@ forEach(iteratorFn: ListIteratorFn<T>): void
Runs a function on all nodes in a linked list from head to tail:
```js
list.addManyTail(['a', 'b', 'c']);
list.addTailMany(['a', 'b', 'c']);
// "a" <-> "b" <-> "c"
@ -1425,7 +1425,7 @@ list.forEach((node, index) => console.log(node.value + index));
A linked list is iterable. In other words, you may use methods like `for...of` on it.
```js
list.addManyTail(['a', 'b', 'c']);
list.addTailMany(['a', 'b', 'c']);
// "a" <-> "b" <-> "c"
@ -1449,7 +1449,7 @@ toArray(): T[]
Converts a linked list to an array of values:
```js
list.addManyTail(['a', 'b', 'c']);
list.addTailMany(['a', 'b', 'c']);
// "a" <-> "b" <-> "c"
@ -1471,7 +1471,7 @@ toNodeArray(): ListNode<T>[]
Converts a linked list to an array of nodes:
```js
list.addManyTail(['a', 'b', 'c']);
list.addTailMany(['a', 'b', 'c']);
// "a" <-> "b" <-> "c"
@ -1495,7 +1495,7 @@ toString(mapperFn: ListMapperFn<T> = JSON.stringify): string
Converts a linked list to a string representation of nodes and their relations:
```js
list.addManyTail(['a', 2, 'c', { k: 4, v: 'd' }]);
list.addTailMany(['a', 2, 'c', { k: 4, v: 'd' }]);
// "a" <-> 2 <-> "c" <-> {"k":4,"v":"d"}
@ -1562,7 +1562,7 @@ export class ListNode<T = any> {
- `previous` refers to the previous node in the list.
```js
list.addManyTail([ 0, 1, 2 ]);
list.addTailMany([ 0, 1, 2 ]);
console.log(
list.head.value, // 0

27
docs/en/docs-nav.json

@ -221,11 +221,8 @@
},
{
"text": "Domain Driven Design",
"path": "Domain-Driven-Design.md",
"items": [
{
"text": "Overall",
"path": "Domain-Driven-Design.md"
},
{
"text": "Domain Layer",
"items": [
@ -340,22 +337,10 @@
"text": "Permission Management",
"path": "UI/Angular/Permission-Management.md"
},
{
"text": "Confirmation Popup",
"path": "UI/Angular/Confirmation-Service.md"
},
{
"text": "Toast Overlay",
"path": "UI/Angular/Toaster-Service.md"
},
{
"text": "Config State",
"path": "UI/Angular/Config-State.md"
},
{
"text": "Modifying the Menu",
"path": "UI/Angular/Modifying-the-Menu.md"
},
{
"text": "Component Replacement",
"path": "UI/Angular/Component-Replacement.md"
@ -400,11 +385,8 @@
},
{
"text": "Data Access",
"path": "Data-Access.md",
"items": [
{
"text": "Overall",
"path": "Data-Access.md"
},
{
"text": "Connection Strings",
"path": "Connection-Strings.md"
@ -487,11 +469,8 @@
},
{
"text": "Startup Templates",
"path": "Startup-Templates/Index.md",
"items": [
{
"text": "Overall",
"path": "Startup-Templates/Index.md"
},
{
"text": "Application",
"path": "Startup-Templates/Application.md"

4
docs/zh-Hans/API/Auto-API-Controllers.md

@ -6,7 +6,7 @@ ABP可以按照惯例 **自动** 将你的应用程序服务配置为API控制
## 配置
基本配置很简单. 只需配置`AbpAspNetCoreMvcOptions`并使用`ConventionalControllers.Create`方法,如下所示:
基本配置很简单. 只需配置`AbpAspNetCoreMvcOptions`并使用`ConventionalControllers.Create`方法,如下所示:
````csharp
[DependsOn(BookStoreApplicationModule)]
@ -82,7 +82,7 @@ Configure<AbpAspNetCoreMvcOptions>(options =>
* 删除'**Async**'后缀. 如果方法名称为'GetPhonesAsync',则变为`GetPhones`.
* 删除**HTTP method前缀**. 基于的HTTP method删除`GetList`,`GetAll`,`Get`,`Put`,`Update`,`Delete`,`Remove`,`Create`,`Add`,`Insert`,`Post`和`Patch`前缀, 因此`GetPhones`变为`Phones`, 因为`Get`前缀和GET请求重复.
* 将结果转换为**camelCase**.
* 如果生成的操作名称为**空**,则它不会添加到路径中.否则它会被添加到路由中(例如'/phones').对于`GetAllAsync`方法名称,它将为空,因为`GetPhonesAsync`方法名称将为`phone`.
* 如果生成的操作名称为**空**,则它不会添加到路径中.否则它会被添加到路由中(例如'/phones').对于`GetAllAsync`方法名称它将为空,因为`GetPhonesAsync`方法名称将为`phone`.
* 可以通过设置`UrlActionNameNormalizer`选项来自定义.It's an action delegate that is called for every method.
* 如果有另一个带有'Id'后缀的参数,那么它也会作为最终路线段添加到路线中(例如'/phoneId').

2
docs/zh-Hans/Application-Services.md

@ -380,4 +380,4 @@ public class DistrictKey
### 生命周期
应用服务的生命周期是[transient](Dependency-Injection)的,它们会自动注册到依赖注入系统.
应用服务的生命周期是[transient](Dependency-Injection)的它们会自动注册到依赖注入系统.

2
docs/zh-Hans/AspNetCore/Tag-Helpers/Buttons.md

@ -86,7 +86,7 @@ ABP框架定义了Tag Helper用于简单的创建bootstrap按钮.
### `icon-type`
`icon-type` 是一个可选参数.它的默认值是 `FontAwesome`. 你可以创建自己的图标类型提供程序并更改它.
`icon-type` 是一个可选参数它的默认值是 `FontAwesome`. 你可以创建自己的图标类型提供程序并更改它.
你可以为按钮选择以下图标类型:

32
docs/zh-Hans/Audit-Logging.md

@ -43,12 +43,12 @@ Configure<AbpAuditingOptions>(options =>
* `IsEnabledForGetRequests` (默认值: `false`): HTTP GET请求通常不应该在数据库进行任何更改,审计日志系统不会为GET请求保存审计日志对象. 将此值设置为 `true` 可为GET请求启用审计日志系统.
* `ApplicationName`: 如果有多个应用程序保存审计日志到单一的数据库,使用此属性设置为你的应用程序名称区分不同的应用程序日志.
* `IgnoredTypes`: 审计日志系统忽略的 `Type` 列表. 如果它是实体类型,则不会保存此类型实体的更改. 在序列化操作参数时也使用此列表.
* `EntityHistorySelectors`:选择器列表,用于确定是否选择了用于保存实体更改的实体类型. 有关详细信息请参阅下面的部分.
* `EntityHistorySelectors`选择器列表,用于确定是否选择了用于保存实体更改的实体类型. 有关详细信息请参阅下面的部分.
* `Contributors`: `AuditLogContributor` 实现的列表. 贡献者是扩展审计日志系统的一种方式. 有关详细信息请参阅下面的"审计日志贡献者"部分.
### 实体历史选择器
保存的所有实体的所有变化将需要大量的数据库空间. 出于这个原因**审计日志系统不保存为实体的任何改变,除非你明确地对其进行配置**.
保存的所有实体的所有变化将需要大量的数据库空间. 出于这个原因**审计日志系统不保存为实体的任何改变,除非你明确地对其进行配置**.
要保存的所有实体的所有更改,只需使用 `AddAllEntities()` 扩展方法.
@ -131,7 +131,7 @@ public class HomeController : AbpController
可以为任何类型的类(注册到[依赖注入](Dependency-Injection.md)并从依赖注入解析)启用审计日志,默认情况下仅对控制器和应用程序服务启用.
对于任何需要被审计记录的类或方法都可以使用 `[Audited]` 和`IAuditingEnabled`.此外,你的类可以(直接或固有的)实现 `IAuditingEnabled` 接口以认启用该类的审计日志记录.
对于任何需要被审计记录的类或方法都可以使用 `[Audited]` 和`IAuditingEnabled`.此外,您的类可以(直接或固有的)实现 `IAuditingEnabled` 接口以认启用该类的审计日志记录.
### 启用/禁用 实体 & 属性
@ -211,8 +211,8 @@ public class MyUser : Entity<Guid>
* **AuditLogInfo**: 具有以下属性:
* `ApplicationName`: 当你保存不同的应用审计日志到同一个数据库,这个属性用来区分应用程序.
* `UserId`:当前用户的Id,用户未登录为 `null`.
* `UserName`:当前用户的用户名,如果用户已经登录(这里的值不依赖于标识模块/系统进行查找).
* `UserId`当前用户的Id,用户未登录为 `null`.
* `UserName`当前用户的用户名,如果用户已经登录(这里的值不依赖于标识模块/系统进行查找).
* `TenantId`: 当前租户的Id,对于多租户应用.
* `TenantName`: 当前租户的名称,对于多租户应用.
* `ExecutionTime`: 审计日志对象创建的时间.
@ -222,28 +222,28 @@ public class MyUser : Entity<Guid>
* `ClientIpAddress`: 客户端/用户设备的IP地址.
* `CorrelationId`: 当前[相关Id](CorrelationId.md). 相关Id用于在单个逻辑操作中关联由不同应用程序(或微服务)写入的审计日志.
* `BrowserInfo`: 当前用户的浏览器名称/版本信息,如果有的话.
* `HttpMethod`: 当前HTTP请求的方法(GET,POST,PUT,DELETE ...等).
* `HttpMethod`: 当前HTTP请求的方法(GET,POST,PUT,DELETE ...等).
* `HttpStatusCode`: HTTP响应状态码.
* `Url`: 请求的URL.
* **AuditLogActionInfo**: 一个 审计日志动作通常是web请求期间控制器动作或[应用服务](Application-Services.md)方法调用. 一个审计日志可以包含多个动作. 动作对象具有以下属性:
* `ServiceName`:执行的控制器/服务的名称.
* `MethodName`:控制器/服务执行的方法的名称.
* `Parameters`:传递给方法的参数的JSON格文本.
* `ServiceName`执行的控制器/服务的名称.
* `MethodName`控制器/服务执行的方法的名称.
* `Parameters`传递给方法的参数的JSON格文本.
* `ExecutionTime`: 执行的时间.
* `ExecutionDuration`: 方法执行时长,以毫秒为单位. 可以用来观察方法的性能.
* **EntityChangeInfo**: 表示一个实体在Web请求中的变更. 审计日志可以包含0个或多个实体的变更. 实体变更具有以下属性:
* `ChangeTime`: 当实体被改变的时间.
* `ChangeType`:具有以下字段的枚举: `Created`(0), `Updated`(1)和 `Deleted`(2).
* `ChangeType`具有以下字段的枚举: `Created`(0), `Updated`(1)和 `Deleted`(2).
* `EntityId`: 更改实体的Id.
* `EntityTenantId`:实体所属的租户Id.
* `EntityTenantId`实体所属的租户Id.
* `EntityTypeFullName`: 实体的类型(类)的完整命名空间名称(例如Book实体的*Acme.BookStore.Book*.
* **EntityPropertyChangeInfo**: 表示一个实体的属性的更改.一个实体的更改信息(上面已说明)可含有具有以下属性的一个或多个属性的更改:
* `NewValue`: 属性的新值. 如果实体已被删除为 `null`.
* `OriginalValue`:变更前旧/初始值. 如果实体是新创建为 `null`.
* `OriginalValue`变更前旧/初始值. 如果实体是新创建为 `null`.
* `PropertyName`: 实体类的属性名称.
* `PropertyTypeFullName`:属性类型的完整命名空间名称.
* `PropertyTypeFullName`属性类型的完整命名空间名称.
* **Exception**: 审计日志对象可能包含零个或多个异常. 可以得到失败请求的异常信息.
* **Comment**:用于将自定义消息添加到审计日志条目的任意字符串值. 审计日志对象可能包含零个或多个注释.
* **Comment**用于将自定义消息添加到审计日志条目的任意字符串值. 审计日志对象可能包含零个或多个注释.
除了上面说明的标准属性之外,`AuditLogInfo`, `AuditLogActionInfo``EntityChangeInfo` 对象还实现了`IHasExtraProperties` 接口,你可以向这些对象添加自定义属性.
@ -331,7 +331,7 @@ public class MyService : ITransientDependency
### 手动创建审计日志范围
你很少需要手动创建审计日志的范围,但如果你需要,可以使用 `IAuditingManager` 创建审计日志的范围.
:
````csharp
public class MyService : ITransientDependency
@ -366,7 +366,7 @@ public class MyService : ITransientDependency
}
````
可以调用其他服务,它们可能调用其他服务,它们可能更改实体,等等. 所有这些交互都保存为finally块中的一个审计日志对象.
可以调用其他服务,它们可能调用其他服务,它们可能更改实体,等等. 所有这些交互都保存为finally块中的一个审计日志对象.
## 审计日志模块

2
docs/zh-Hans/Authorization.md

@ -153,7 +153,7 @@ myGroup.AddPermission(
myGroup.AddPermission("Author_Management", isEnabled: false);
````
通常你不需要定义禁用权限(除非暂时想要禁用应用程序的功能). 无论怎样,你可能想要禁用依赖模块中定义的权限,这样你可以禁用相关的功能. 参阅下面的 "*更改依赖模块的权限定义*" 节,查看示例用法.
通常你不需要定义禁用权限(除非暂时想要禁用应用程序的功能). 无论怎样,你可能想要禁用依赖模块中定义的权限,这样你可以禁用相关的功能. 参阅下面的 "*更改依赖模块的权限定义*" 节,查看示例用法.
> 注意:检查一个未定义的权限会抛出异常,而被禁用的权限的返回禁止(false).

4
docs/zh-Hans/Background-Workers.md

@ -2,7 +2,7 @@
## 介绍
背景工人在应用简单独立的线程在后台运行.一般来说,他们定期运行,以执行一些任务.例子;
背景工人在应用简单独立的线程在后台运行。一般来说,他们定期运行,以执行一些任务。例子;
后台工作者在应用程序后台运行的简单的独立线程,一般来说它们定期运行执行一些任务.例如;
* 后台工作者可以定期**删除过时的日志**.
@ -72,7 +72,7 @@ public class PassiveUserCheckerWorker : AsyncPeriodicBackgroundWorkerBase
}
````
* `AsyncPeriodicBackgroundWorkerBase` 使用 `AbpTimer`(线程安全定时器)对象来确定**时间段**. 我们可以在构造函数中设置了`Period` 属性.
* `AsyncPeriodicBackgroundWorkerBase` 使用 `AbpTimer`(线程安全定时器)对象来确定**时间段**. 我们可以在构造函数中设置了`Period` 属性
* 它需要实现 `DoWorkAsync` 方法**执行**定期任务.
* 最好使用 `PeriodicBackgroundWorkerContext` **解析依赖** 而不是构造函数. 因为 `AsyncPeriodicBackgroundWorkerBase` 使用 `IServiceScope` 在你的任务执行结束时会对其 **disposed**.
* `AsyncPeriodicBackgroundWorkerBase` **捕获并记录**`DoWorkAsync` 方法抛出的 **异常**.

14
docs/zh-Hans/Blog-Posts/2019-02-22/Post.md

@ -13,7 +13,7 @@ ABP框架的主要目标之一是提供[创建微服务解决方案的便利基
- 使用[Ocelot](https://github.com/ThreeMammals/Ocelot)库开发了多个**网关** / BFF(后端为前端(Backend for Frontends)).
- 使用[IdentityServer](https://identityserver.io/)框架开发**身份验证服务**.它也是一个带有必要UI的SSO(单点登录)应用程序.
- 有**多个数据库**.一些微服务有自己的数据库,而一些服务/应用程序共享一个数据库(以演示不同的用例).
- 具有不同类型的数据库:**SQL Server**(使用**Entity Framework Core** ORM)和**MongoDB**.
- 具有不同类型的数据库**SQL Server**(使用**Entity Framework Core** ORM)和**MongoDB**.
- 有一个**控制台应用程序**来显示通过身份验证使用服务的最简单方法.
- 使用[Redis](https://redis.io/)进行**分布式缓存**.
- 使用[RabbitMQ](https://www.rabbitmq.com/)进行服务到服务(service-to-service)的**消息传递**.
@ -28,27 +28,27 @@ ABP框架的主要目标之一是提供[创建微服务解决方案的便利基
## 路线图
在第一个稳定版本(v1.0)之前还有很多工作要做.可以在GitHub仓库上看到[优先的积压项目](https://github.com/abpframework/abp/issues?q=is%3Aopen+is%3Aissue+milestone%3ABacklog).
在第一个稳定版本(v1.0)之前还有很多工作要做.可以在GitHub仓库上看到[优先的积压项目](https://github.com/abpframework/abp/issues?q=is%3Aopen+is%3Aissue+milestone%3ABacklog).
根据我们的估计,我们计划在2019年第二季度(可能在五月或六月)发布v1.0.所以,不用等待太长时间了.我们也对第一个稳定版本感到非常兴奋.
我们还将完善[文档](https://abp.io/documents/abp/latest),因为它现在还远未完成.
第一个版本可能不包含SPA模板.但是,如果可能的话,我们想要准备一个简单些的.SPA框架还没有确定下来.备选有:**Angular,React和Blazor**.请将的想法写为对此帖的评论.
第一个版本可能不包含SPA模板.但是,如果可能的话,我们想要准备一个简单些的.SPA框架还没有确定下来.备选有:**Angular,React和Blazor**.请将的想法写为对此帖的评论.
## 中文网
中国有一个大型的ABP社区.他们创建了一个中文版的abp.io网站:https://abp.io/. 他们一直在保持更新.感谢中国的开发人员,特别是[Liming Ma](https://github.com/maliming).
中国有一个大型的ABP社区.他们创建了一个中文版的abp.io网站https://abp.io/. 他们一直在保持更新.感谢中国的开发人员,特别是[Liming Ma](https://github.com/maliming).
## NDC {London} 2019
很高兴作为合作伙伴参加[NDC {London}](https://ndc-london.com/)2019 .我们已经与许多开发人员讨论过当前的ASP.NET Boilerplate和ABP vNext,我们得到了很好的反馈.
我们还有机会与[Scott Hanselman](https://twitter.com/shanselman)和[Jon Galloway](https://twitter.com/jongalloway)交谈.他们参观了我们的展位,我们谈到了ABP vNext的想法.他们喜欢新的ABP框架的功能,方法和目标.在twitter上查看一些照片和评论:
我们还有机会与[Scott Hanselman](https://twitter.com/shanselman)和[Jon Galloway](https://twitter.com/jongalloway)交谈.他们参观了我们的展位,我们谈到了ABP vNext的想法.他们喜欢新的ABP框架的功能,方法和目标.在twitter上查看一些照片和评论
![scott-and-jon](scott-and-jon.png)
## 跟上步伐
* 你可以标星并关注**GitHub**存储库:https://github.com/abpframework/abp
* 你可以关注官方**Twitter**帐户获取新闻:https://twitter.com/abpframework
* 您可以标星并关注**GitHub**存储库:https://github.com/abpframework/abp
* 您可以关注官方**Twitter**帐户获取新闻:https://twitter.com/abpframework

12
docs/zh-Hans/Blog-Posts/2019-06-19 v0_18_Release/Post.md

@ -4,7 +4,7 @@ ABP v0.18已发布, 包含解决的[80+个issue](https://github.com/abpframework
## 网站更改
[abp.io](https://abp.io)网站**完全更新**以突出ABP框架的目标和重要功能.文档和博客网址也会更改:
[abp.io](https://abp.io)网站**完全更新**以突出ABP框架的目标和重要功能.文档和博客网址也会更改
- `abp.io/documents`移至[docs.abp.io](https://docs.abp.io).
- `abp.io/blog`转移到[blog.abp.io](https://blog.abp.io).
@ -21,25 +21,25 @@ ABP CLI现在是创建新项目的首选方式,你仍然可以从[开始](https:
### 用法
使用命令行窗口安装ABP CLI:
使用命令行窗口安装ABP CLI
```` bash
dotnet tool install -g Volo.Abp.Cli
````
创建一个新应用程序:
创建一个新应用程序
```` bash
abp new Acme.BookStore
````
将模块添加到应用程序:
将模块添加到应用程序
```` bash
abp add-module Volo.Blogging
````
更新解决方案中所有与ABP相关的包:
更新解决方案中所有与ABP相关的包
```` bash
abp update
@ -59,7 +59,7 @@ abp update
## 更改日志
以下是此版本附带的一些其他功能和增强功能:
以下是此版本附带的一些其他功能和增强功能
* 新[Volo.Abp.Dapper](https://www.nuget.org/packages/Volo.Abp.Dapper)包.
* 新[Volo.Abp.Specifications](https://www.nuget.org/packages/Volo.Abp.Specifications)包.

4
docs/zh-Hans/Blog-Posts/2019-08-16 v0_19_Release/Post.md

@ -14,12 +14,12 @@ ABP v0.19已发布,包含解决的[~90个问题](https://github.com/abpframework
* 更新了[ABP CLI](https://docs.abp.io/en/abp/latest/CLI)和[下载页面](https://abp.io/get-started),以便能够使用新的UI选项生成项目.
* 创建了[教程](https://docs.abp.io/en/abp/latest/Tutorials/Angular/Part-I)以使用新的UI选项快速入门.
我们基于最新的Angular工具和趋势创建了模板,文档和基础架构:
我们基于最新的Angular工具和趋势创建了模板,文档和基础架构
* 使用[NgBootstrap](https://ng-bootstrap.github.io/)和[PrimeNG](https://www.primefaces.org/primeng/)作为UI组件库.你可以使用自己喜欢的库,没问题,但预构建的模块可以使用这些库.
* 使用[NGXS](https://ngxs.gitbook.io/ngxs/)作为状态管理库.
Angular是第一个SPA UI选项,但它不是最后一个.在v1.0发布之后,我们将开始第二个UI选项的工作.虽然尚未决定,但候选的有Blazor,React和Vue.js. 等待你的反馈.你可以使用以下issue进行投票(thumb):
Angular是第一个SPA UI选项,但它不是最后一个.在v1.0发布之后,我们将开始第二个UI选项的工作.虽然尚未决定,但候选的有Blazor,React和Vue.js. 等待你的反馈.你可以使用以下issue进行投票(thumb)
* [Blazor](https://github.com/abpframework/abp/issues/394)
* [Vue.js](https://github.com/abpframework/abp/issues/1168)

4
docs/zh-Hans/Blog-Posts/2019-09-25 v0_21_Release/Post.md

@ -14,8 +14,8 @@ ABP框架越来越接近v1.0.我们打算在今年10月中旬发布1.0. 现在,
## Techorama荷兰2019
[Techorama NL](https://techorama.nl/)是欧洲最大的会议之一.今年,Volosoft是会议的赞助商,并将有一个展位与软件开发人员讨论ABP框架和软件开发.我们的展位墙如下图所示:
[Techorama NL](https://techorama.nl/)是欧洲最大的会议之一.今年,Volosoft是会议的赞助商,并将有一个展位与软件开发人员讨论ABP框架和软件开发.我们的展位墙如下图所示
![volosoft-booth](volosoft-booth.png)
如果你也参加会议,请到展位讨论ABP框架.我们还为你准备了一些私货:)
如果您也参加会议,请到展位讨论ABP框架.我们还为您准备了一些私货:)

16
docs/zh-Hans/Blog-Posts/2020-03-19 v2_3_Release/Post.md

@ -24,7 +24,7 @@
我们终于完成了**react native移动应用程序**.目前,它可以让你**登录**,管理**用户**和**租户**.它利用ABP框架相同的设置,授权和本地化系统.
应用程序的一些截图:
应用程序的一些截图
![mobile-ui](react-native-ui.png)
@ -34,7 +34,7 @@
从我们的Angular应用程序中调用服务器中的REST端点是很常见的.这种情况下,我们一般创建**服务**(在服务器上包含各个服务的方法)和**模型对象**(对应服务器上的[DTO](https://docs.abp.io/en/abp/latest/Data-Transfer-Objects)).
除了手动创建这样的与服务器交互的服务外,我们可以使用像[NSWAG](https://github.com/RicoSuter/NSwag)工具来为我们生成服务代理.但是NSWAG有以下几个我们遇到的问题:
除了手动创建这样的与服务器交互的服务外,我们可以使用像[NSWAG](https://github.com/RicoSuter/NSwag)工具来为我们生成服务代理.但是NSWAG有以下几个我们遇到的问题
* 它产生一个**大,单一**的.ts文件;
* 当你的应用程序增长时,它变得**太大**了.
@ -58,12 +58,12 @@ abp generate-proxy
### 添加模块的源代码
应用程序启动模板带有一些[应用模块](https://docs.abp.io/en/abp/latest/Modules/Index), 以**Nuget和NPM包**的方式**预先安装了** .这样做有几个重要的优点:
应用程序启动模板带有一些[应用模块](https://docs.abp.io/en/abp/latest/Modules/Index), 以**Nuget和NPM包**的方式**预先安装了** .这样做有几个重要的优点
* 当新版本可用时, 你可以 **轻松地[升级](https://docs.abp.io/en/abp/latest/CLI#update)** 这些模块.
* 你的解决方案**更干净**,这样你就可以专注于自己的代码.
但是,当你需要对一个依赖的模块**大量定制**时,就不如它的代码在你的应用程序中那么容易.为了解决这个问题,我们引入了一个[ABP CLI](https://docs.abp.io/en/abp/latest/CLI)的新命令, 在你的解决方案中用代码**替换**Nuget包.用法很简单:
但是,当你需要对一个依赖的模块**大量定制**时,就不如它的代码在你的应用程序中那么容易.为了解决这个问题,我们引入了一个[ABP CLI](https://docs.abp.io/en/abp/latest/CLI)的新命令, 在你的解决方案中用代码**替换**Nuget包.用法很简单
````bash
abp add-module --with-source-code
@ -75,19 +75,19 @@ abp add-module --with-source-code
此外,我们也创建了文档来说明如何定制依赖的模块而不改变它们的源代码(见下面的部分).仍然建议以包的方式使用模块,以便在以后可以轻松升级.
> 免费模块的源代码是**MIT**许可,所以你可以自由更改它们并添加到的解决方案中.
> 免费模块的源代码是**MIT**许可,所以你可以自由更改它们并添加到的解决方案中.
### 切换到预览版
ABP框架正在迅速发展,我们经常发布新版本.不过,如果你想更紧密地追随它,你可以使用**每日预览包**.
我们创建了一个ABP CLI命令来轻松地为你的解决方案**更新到最新的预览包**.在你的解决方案的根文件夹中运行以下命令:
我们创建了一个ABP CLI命令来轻松地为你的解决方案**更新到最新的预览包**.在你的解决方案的根文件夹中运行以下命令
````bash
abp switch-to-preview
````
它会修改所有ABP相关的NuGet和NPM包的版本.当你需要时你也可以**切换回最新稳定版**:
它会修改所有ABP相关的NuGet和NPM包的版本.当你需要时你也可以**切换回最新稳定版**
````bash
abp switch-to-stable
@ -131,7 +131,7 @@ abp switch-to-stable
## 下一步?
我们未来几个月的目标如下:
我们未来几个月的目标如下
* 完成**文档和示例**,写更多的教程.
* 使框架和现有模块的更加**可定制和可扩展**.

20
docs/zh-Hans/CLI.md

@ -41,12 +41,12 @@ abp new Acme.BookStore
* `--template` 或者 `-t`: 指定模板. 默认的模板是 `app`,会生成web项目.可用的模板有:
* `app` (default): [应用程序模板](Startup-Templates/Application.md). 其他选项:
* `--ui` 或者 `-u`: 指定ui框架.默认`mvc`框架.其他选项:
* `mvc`: ASP.NET Core MVC.此模板的其他选项:
* `--ui` 或者 `-u`: 指定ui框架.默认`mvc`框架.其他选项
* `mvc`: ASP.NET Core MVC.此模板的其他选项
* `--tiered`: 创建分层解决方案,Web和Http Api层在物理上是分开的.如果未指定会创建一个分层的解决方案,此解决方案没有那么复杂,适合大多数场景.
* `angular`: Angular. 这个模板还有一些额外的选项:
* `angular`: Angular. 这个模板还有一些额外的选项
* `--separate-identity-server`: 将Identity Server应用程序与API host应用程序分开. 如果未指定,则服务器端将只有一个端点.
* `none`: 无UI. 这个模板还有一些额外的选项:
* `none`: 无UI. 这个模板还有一些额外的选项
* `--separate-identity-server`: 将Identity Server应用程序与API host应用程序分开. 如果未指定,则服务器端将只有一个端点.
* `--mobile` 或者 `-m`: 指定移动应用程序框架. 默认框架是 `react-native`. 其他选项:
* `none`: 不包含移动应用程序.
@ -57,7 +57,7 @@ abp new Acme.BookStore
* `module`: [Module template](Startup-Templates/Module.md). 其他选项:
* `--no-ui`: 不包含UI.仅创建服务模块(也称为微服务 - 没有UI).
* `--output-folder` 或者 `-o`: 指定输出文件夹,默认是当前目录.
* `--version` 或者 `-v`: 指定ABP和模板的版本.它可以是 [release tag](https://github.com/abpframework/abp/releases) 或者 [branch name](https://github.com/abpframework/abp/branches). 如果没有指定,则使用最新版本.大多数情况下,会希望使用最新的版本.
* `--version` 或者 `-v`: 指定ABP和模板的版本.它可以是 [release tag](https://github.com/abpframework/abp/releases) 或者 [branch name](https://github.com/abpframework/abp/branches). 如果没有指定,则使用最新版本.大多数情况下,会希望使用最新的版本.
* `--template-source` 或者 `-ts`: 指定自定义模板源用于生成项目,可以使用本地源和网络源(例如 `D\localTemplate``https://<your url>.zip`).
* `--create-solution-folder` 或者 `-csf`: 指定项目是在输出文件夹中的新文件夹中还是直接在输出文件夹中.
* `--connection-string` 或者 `-cs`: 重写所有 `appsettings.json` 文件的默认连接字符串. 默认连接字符串是 `Server=localhost;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true`. 如果你不想使用默认,你可以设置自己的连接字符串. 默认的数据库提供程序是 `SQL Server`, 所以你只能输入SQL Server连接字符串!
@ -135,8 +135,6 @@ abp update [options]
* `--include-previews``-p`: 将预览版, 测试版本 和 rc 包 同时更新到最新版本.
* `--npm`: 仅更新NPM包
* `--nuget`: 仅更新的NuGet包
* `--solution-path``-sp`: 指定解决方案路径/目录. 默认使用当前目录
* `--solution-name``-sn`: 指定解决方案名称. 默认在目录中搜索`*.sln`文件.
### 切换到每晚构建(预览)包
@ -166,10 +164,6 @@ CLI的一些功能需要登录到abp.io平台. 使用你的用户名登录
abp login <username>
```
```bash
abp login <username> -p <password>
```
请注意,新的登录将终止先前的会话并创建一个新的会话.
### logout
@ -192,9 +186,9 @@ abp generate-proxy [options]
#### Options
* `--apiUrl` 或者 `-a`:指定HTTP API的根URL. 如果未指定这个选项,默认使用你Angular应用程序的`environment.ts`文件API URL. 在运行 `generate-proxy` 命令之前,你的host必须启动正在运行.
* `--apiUrl` 或者 `-a`指定HTTP API的根URL. 如果未指定这个选项,默认使用你Angular应用程序的`environment.ts`文件API URL. 在运行 `generate-proxy` 命令之前,你的host必须启动正在运行.
* `--ui` 或者 `-u`: 指定UI框架,默认框架是angular.当前只有angular一个选项, 但我们会通过更改CLI增加新的选项. 尽请关注!
* `--module` 或者 `-m`:指定模块名. 默认模块名称为app. 如果你想所有模块,你可以指定 `--module all` 命令.
* `--module` 或者 `-m`指定模块名. 默认模块名称为app. 如果你想所有模块,你可以指定 `--module all` 命令.
示例:

2
docs/zh-Hans/Customizing-Application-Modules-Overriding-Services.md

@ -54,7 +54,7 @@ context.Services.Replace(
## 重写一个服务类
大多数情况下,你会仅想改变服务当前实现的一个或几个方法. 重新实现完整的接口变的繁琐,更好的方法是继承原始类并重写方法.
大多数情况下,你会仅想改变服务当前实现的一个或几个方法. 重新实现完整的接口变的繁琐,更好的方法是继承原始类并重写方法
### 示例: 重写服务方法

6
docs/zh-Hans/Entities.md

@ -72,7 +72,7 @@ public class BookAppService : ApplicationService, IBookAppService
* `BookAppService` 注入图书实体的默认[仓库](Repositories.md),使用`InsertAsync`方法插入 `Book` 到数据库中.
* `GuidGenerator`类型是 `IGuidGenerator`,它是在`ApplicationService`基类中定义的属性. ABP将这样常用属性预注入,所以不需要手动[注入](Dependency-Injection.md).
* 如果你想遵循DDD最佳实践,请参阅下面的*聚合示例*部分.
* 如果您想遵循DDD最佳实践,请参阅下面的*聚合示例*部分.
### 具有复合键的实体
@ -228,7 +228,7 @@ ABP框架不强制你应用任何DDD规则或模式.但是,当你准备应用的
## 基类和接口的审计属性
有一些属性,像`CreationTime`,`CreatorId`,`LastModificationTime`...在所有应用中都很常见. ABP框架提供了一些接口和基类来**标准化**这些属性,并**自动设置它们的值**.
有一些属性,像`CreationTime`,`CreatorId`,`LastModificationTime`...在所有应用中都很常见. ABP框架提供了一些接口和基类来**标准化**这些属性,并**自动设置它们的值**.
### 审计接口
@ -285,7 +285,7 @@ ABP框架不强制你应用任何DDD规则或模式.但是,当你准备应用的
所有这些基类都有非泛型版本,可以使用 `AuditedEntity``FullAuditedAggregateRoot` 来支持复合主键;
所有这些基类也有 `... WithUser`,`FullAuditedAggregateRootWithUser<TUser>``FullAuditedAggregateRootWithUser<TKey, TUser>`. 这样就可以将导航属性添加到你的用户实体. 但在聚合根之间添加导航属性不是一个好做法,所以这种用法是不建议的(除非你使用EF Core之类的ORM可以很好地支持这种情况,并且你真的需要它. 请记住这种方法不适用于NoSQL数据库(如MongoDB),你必须真正实现聚合模式).
所有这些基类也有 `... WithUser``FullAuditedAggregateRootWithUser<TUser>``FullAuditedAggregateRootWithUser<TKey, TUser>`. 这样就可以将导航属性添加到你的用户实体. 但在聚合根之间添加导航属性不是一个好做法,所以这种用法是不建议的(除非你使用EF Core之类的ORM可以很好地支持这种情况,并且你真的需要它. 请记住这种方法不适用于NoSQL数据库(如MongoDB),你必须真正实现聚合模式).
## 额外的属性

14
docs/zh-Hans/Entity-Framework-Core-Migrations.md

@ -1,7 +1,7 @@

# EF Core数据库迁移
本文首先介绍[应用程序启动模板](Startup-Templates/Application.md)提供的**默认结构**,并讨论可能希望为自己的应用程序实现的**各种场景**.
本文首先介绍[应用程序启动模板](Startup-Templates/Application.md)提供的**默认结构**,并讨论可能希望为自己的应用程序实现的**各种场景**.
> 本文档适用于希望完全理解和自定义[应用程序启动模板](Startup-Templates/Application.md)附带的数据库结构的人员. 如果你只是想创建实体和管理代码优先(code first)迁移,只需要遵循[启动教程](Tutorials/Index.md).
@ -95,7 +95,7 @@ Volo.Abp.IdentityServer.AbpIdentityServerDbProperties.DbTablePrefix = "Ids";
这个项目有应用程序的 `DbContext`类(本例中的 `BookStoreDbContex` ).
**每个模块都使用自己的 `DbContext` 类**来访问数据库.同样你的应用程序有它自己的 `DbContext`. 通常在应用程序中使用这个 `DbContet`(如果你遵循最佳实践,应该在[仓储](Repositories.md)中使用). 它几乎是一个空的 `DbContext`,因为你的应用程序在一开始没有任何实体,除了预定义的 `AppUser` 实体:
**每个模块都使用自己的 `DbContext` 类**来访问数据库同样你的应用程序有它自己的 `DbContext`. 通常在应用程序中使用这个 `DbContet`(如果你遵循最佳实践,应该在[仓储](Repositories.md)中使用). 它几乎是一个空的 `DbContext`,因为你的应用程序在一开始没有任何实体,除了预定义的 `AppUser` 实体:
````csharp
[ConnectionStringName("Default")]
@ -268,10 +268,10 @@ public class BackgroundJobsDbContext
##### 重用模块的表
可能想在应用程序中**重用依赖模块的表**. 在这种情况下你有两个选择:
可能想在应用程序中**重用依赖模块的表**. 在这种情况下你有两个选择:
1. 你可以**直接使用模块定义的实体**(你仍然可以在某种程度上[扩展实体](Customizing-Application-Modules-Extending-Entities.md)).
2. 你可以**创建一个新的实体**映射到同一个数据库表.
2. 你可以**创建一个新的实体**映射到同一个数据库表
###### 使用由模块定义的实体
@ -307,7 +307,7 @@ namespace Acme.BookStore
示例注入了 `IRepository<IdentityUser,Guid>`(默认仓储). 它定义了标准的存储库方法并实现了 `IQueryable` 接口.
另外,身份模块定义了 `IIdentityUserRepository`(自定义仓储),你的应用程序也可以注入和使用它. `IIdentityUserRepository``IdentityUser` 实体提供了额外的定制方法,但它没有实现 `IQueryable`.
另外身份模块定义了 `IIdentityUserRepository`(自定义仓储),你的应用程序也可以注入和使用它. `IIdentityUserRepository``IdentityUser` 实体提供了额外的定制方法,但它没有实现 `IQueryable`.
###### 创建一个新的实体
@ -352,7 +352,7 @@ namespace Acme.BookStore.Roles
* 它继承了[`AggregateRoot<Guid>`类](Entities.md)和实现了[`IMultiTenant`]接口(Multi-Tenancy.md),因为 `IdentityRole` 也做了同样的继承.
* 你可以添加 `IdentityRole` 实体定义的任何属性. 本例只加了 `TenantId``Name` 属性,因为我们这里只需要它们. 你可以把setters设置为私有(如同本例)以防意外更改身份模块的属性.
* 你可以添加自定义(附加)属性. 本例添加了 `Title` 属性.
* **构造函数是私有的**,所以它不允许直接创建一个新的 `AppRole` 实体.创建角色身份模块的责任. 你可以查询角色,设置/更新自定义属性,但做为最佳实践你不应该在代码中创建和删除角色(尽管没有强制的限制).
* **构造函数是私有的**,所以它不允许直接创建一个新的 `AppRole` 实体创建角色身份模块的责任. 你可以查询角色,设置/更新自定义属性,但做为最佳实践你不应该在代码中创建和删除角色(尽管没有强制的限制).
现在是时候定义EF Core映射. 打开应用程序的 `DbContext` (此示例中是 `BookStoreDbContext` )添加以下属性:
@ -360,7 +360,7 @@ namespace Acme.BookStore.Roles
public DbSet<AppRole> Roles { get; set; }
````
然后在 `OnModelCreating` 方法中配置映射(调用 `base.OnModelCreating(builder)` 之后):
然后在 `OnModelCreating` 方法中配置映射(调用 `base.OnModelCreating(builder)` 之后)
````csharp
protected override void OnModelCreating(ModelBuilder builder)

6
docs/zh-Hans/Entity-Framework-Core-MySQL.md

@ -12,12 +12,12 @@
## UseMySQL()
查找你的解决方案中 `UseSqlServer()`调用,替换为 `UseMySQL()`. 检查下列文件:
查找你的解决方案中 `UseSqlServer()`调用替换为 `UseMySQL()`. 检查下列文件:
* `.EntityFrameworkCore` 项目中的*YourProjectName*EntityFrameworkCoreModule.cs.
* `.EntityFrameworkCore` 项目中的*YourProjectName*MigrationsDbContextFactory.cs.
> 根据你的解决方案的结构,你可能发现更多需要改变代码的文件.
> 根据你的解决方案的结构你可能发现更多需要改变代码的文件.
## 更改连接字符串
@ -27,7 +27,7 @@ MySQL连接字符串与SQL Server连接字符串不同. 所以检查你的解决
## 更改迁移DbContext
MySQL DBMS与SQL Server有一些细微的差异. 某些模块数据库映射配置(尤其是字段长度)会导致MySQL出现问题. 例如某些[IdentityServer模块](Modules/IdentityServer.md)表就存在这样的问题,它提供了一个选项可以根据的DBMS配置字段.
MySQL DBMS与SQL Server有一些细微的差异. 某些模块数据库映射配置(尤其是字段长度)会导致MySQL出现问题. 例如某些[IdentityServer模块](Modules/IdentityServer.md)表就存在这样的问题,它提供了一个选项可以根据的DBMS配置字段.
启动模板包含*YourProjectName*MigrationsDbContext,它负责维护和迁移数据库架构. 此DbContext基本上调用依赖模块的扩展方法来配置其数据库表.

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

@ -63,7 +63,7 @@ MySQL连接字符串与SQL Server连接字符串不同. 所以检查你的解决
## 更改迁移DbContext
MySQL DBMS与SQL Server有一些细微的差异. 某些模块数据库映射配置(尤其是字段长度)会导致MySQL出现问题. 例如某些[IdentityServer模块](Modules/IdentityServer.md)表就存在这样的问题,它提供了一个选项可以根据的DBMS配置字段.
MySQL DBMS与SQL Server有一些细微的差异. 某些模块数据库映射配置(尤其是字段长度)会导致MySQL出现问题. 例如某些[IdentityServer模块](Modules/IdentityServer.md)表就存在这样的问题,它提供了一个选项可以根据的DBMS配置字段.
启动模板包含*YourProjectName*MigrationsDbContext,它负责维护和迁移数据库架构. 此DbContext基本上调用依赖模块的扩展方法来配置其数据库表.

4
docs/zh-Hans/Entity-Framework-Core-PostgreSQL.md

@ -12,12 +12,12 @@
## UsePostgreSql()
查找你的解决方案中 `UseSqlServer()`调用,替换为 `UsePostgreSql()`. 检查下列文件:
查找你的解决方案中 `UseSqlServer()`调用替换为 `UsePostgreSql()`. 检查下列文件:
* `.EntityFrameworkCore` 项目中的*YourProjectName*EntityFrameworkCoreModule.cs.
* `.EntityFrameworkCore` 项目中的*YourProjectName*MigrationsDbContextFactory.cs.
> 根据你的解决方案的结构,你可能发现更多需要改变代码的文件.
> 根据你的解决方案的结构你可能发现更多需要改变代码的文件.
## 更改连接字符串

4
docs/zh-Hans/Entity-Framework-Core-SQLite.md

@ -12,12 +12,12 @@
## UseSqlite()
查找你的解决方案中 `UseSqlServer()`调用,替换为 `UseSqlite()`. 检查下列文件:
查找你的解决方案中 `UseSqlServer()`调用替换为 `UseSqlite()`. 检查下列文件:
* `.EntityFrameworkCore` 项目中的*YourProjectName*EntityFrameworkCoreModule.cs.
* `.EntityFrameworkCore` 项目中的*YourProjectName*MigrationsDbContextFactory.cs.
> 根据你的解决方案的结构,你可能发现更多需要改变代码的文件.
> 根据你的解决方案的结构你可能发现更多需要改变代码的文件.
## 更改连接字符串

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

@ -108,7 +108,7 @@ protected override void OnModelCreating(ModelBuilder builder)
### 配置连接字符串选择
如果你的应用程序有多个数据库,你可以使用 `connectionStringName]` Attribute为你的DbContext配置连接字符串名称.
:
```csharp
[ConnectionStringName("MySecondConnString")]
@ -274,7 +274,7 @@ public override async Task DeleteAsync(
## 访问 EF Core API
大多数情况下应该隐藏仓储后面的EF Core API(这也是仓储的设计目地). 但是如果想要通过仓储访问DbContext实现,则可以使用`GetDbContext()`或`GetDbSet()`扩展方法. 例:
大多数情况下应该隐藏仓储后面的EF Core API(这也是仓储的设计目地). 但是如果想要通过仓储访问DbContext实现,则可以使用`GetDbContext()`或`GetDbSet()`扩展方法. 例
````csharp
public class BookService
@ -304,7 +304,7 @@ public class BookService
默认,实体的所有额外属性存储在数据库的一个 `JSON` 对象中.
实体扩展系统允许你存储额外属性在数据库的单独字段中. 有关额外属性和实体扩展系统的更多信息,请参阅下列文档:
实体扩展系统允许你存储额外属性在数据库的单独字段中. 有关额外属性和实体扩展系统的更多信息,请参阅下列文档
* [自定义应用模块: 扩展实体](Customizing-Application-Modules-Extending-Entities.md)
* [实体](Entities.md)
@ -313,7 +313,7 @@ public class BookService
### ObjectExtensionManager.Instance
`ObjectExtensionManager` 实现单例模式,因此你需要使用静态的 `ObjectExtensionManager.Instance` 来执行所有操作.
`ObjectExtensionManager` 实现单例模式因此你需要使用静态的 `ObjectExtensionManager.Instance` 来执行所有操作
### MapEfCoreProperty
@ -417,7 +417,7 @@ context.Services.AddAbpDbContext<BookStoreDbContext>(options =>
});
````
现在,的自定义仓储也可以使用`IBookStoreDbContext`接口:
现在,的自定义仓储也可以使用`IBookStoreDbContext`接口:
````csharp
public class BookRepository : EfCoreRepository<IBookStoreDbContext, Book, Guid>, IBookRepository

9
docs/zh-Hans/Getting-Started-Angular-Template.md

@ -1,8 +1,3 @@
# 启动模板入门
## Getting Started With the Angular Application Template
请参阅以下教程,了解如何使用预构建的应用程序启动模板开始使用ABP框架:
* [ASP.NET Core MVC / Razor Pages UI 入门](Getting-Started?UI=MVC&DB=EF&Tiered=No)
* [Angular UI 入门](Getting-Started?UI=NG&DB=EF&Tiered=No)
<!-- TODO: this document has been moved, it should be deleted in the future. -->
TODO...

4
docs/zh-Hans/Getting-Started-AspNetCore-Application.md

@ -2,7 +2,7 @@
本教程将介绍如何开始以最少的依赖关系开始使用ABP开发.
通常情况下你需要下载一个 ***[启动模板](Getting-Started-AspNetCore-MVC-Template.md)***
通常情况下你需要下载一个 ***[启动模板](https://abp.io/Templates)***
### 创建一个新项目
@ -156,7 +156,7 @@ services.AddApplication<AppModule>(options =>
});
````
4. 更新 `Program.cs`代码, 不再使用`WebHost.CreateDefaultBuilder()`方法(因为它使用默认的DI容器):
4. 更新 `Program.cs`代码, 不再使用`WebHost.CreateDefaultBuilder()`方法(因为它使用默认的DI容器)
````csharp
public class Program

104
docs/zh-Hans/Getting-Started-AspNetCore-MVC-Template.md

@ -1,8 +1,102 @@
# 启动模板入门
## ASP.NET Core MVC 模板入门
请参阅以下教程,了解如何使用预构建的应用程序启动模板开始使用ABP框架:
### 创建新项目
* [ASP.NET Core MVC / Razor Pages UI 入门](Getting-Started?UI=MVC&DB=EF&Tiered=No)
* [Angular UI 入门](Getting-Started?UI=NG&DB=EF&Tiered=No)
本教程使用 **ABP CLI** 创建一个新项目. 更多选项, 请参阅[入门](https://abp.io/get-started)页面.
<!-- TODO: this document has been moved, it should be deleted in the future. -->
如果你之前未安装,请使用命令行安装ABP CLI:
````bash
dotnet tool install -g Volo.Abp.Cli
````
在空文件夹中使用 `abp new` 命令来创建项目:
````bash
abp new Acme.BookStore
````
> 你可以使用不同级别的命名空间; 例如BookStore, Acme.BookStore或Acme.Retail.BookStore.
`new` 命令创建**分层MVC应用程序**, **Entity Framework Core**作为数据库提供程序. 但是,它还有其他选择. 有关所有可用选项,请参见[CLI文档](CLI.md)
#### 预先要求
创建项目的要求:
* [Visual Studio 2019 (v16.4+)](https://visualstudio.microsoft.com/vs/)
* [.NET Core 3.0+](https://www.microsoft.com/net/download/dotnet-core/)
* [Node v12+](https://nodejs.org)
* [Yarn v1.19+](https://classic.yarnpkg.com/)
### 解决方案结构
在**Visual Studio**中打开解决方案:
![bookstore-visual-studio-solution](images/bookstore-visual-studio-solution-v3.png)
该解决方案具有分层结构(基于[Domain Driven Design](Domain-Driven-Design.md)), 并包含配置好的的单元&集成测试项目,可与**EF Core**和**SQLite**数据库内存一起使用.
> 请参阅[应用程序模板文档](Startup-Templates/Application.md)以详细了解解决方案结构.
### 数据库连接字符串
查看`.Web`项目下`appsettings.json`文件中的 **连接字符串**:
````json
{
"ConnectionStrings": {
"Default": "Server=localhost;Database=BookStore;Trusted_Connection=True"
}
}
````
解决方案使用 **Entity Framework Core****MS SQL Server**. EF Core支持[各种](https://docs.microsoft.com/zh-cn/ef/core/providers/)数据库提供程序,因此你可以根据实际需要使用其他DBMS. 如果需要,请更改连接字符串.
### 创建数据库并应用数据库迁移
你有两个选项来创建数据库.
#### 使用DbMigrator应用程序
该解决方案包含一个控制台应用程序(在此示例中名为`Acme.BookStore.DbMigrator`),可以创建数据库,应用迁移和初始化数据. 它对开发和生产环境都很有用.
> `.DbMigrator`项目有自己的`appsettings.json`. 因此,如果你更改了上面的连接字符串,则还应更改此字符串.
右键单击`.DbMigrator`项目并选择 **设置为启动项目**:
![set-as-startup-project](images/set-as-startup-project.png)
按F5(或Ctrl + F5)运行应用程序. 它将具有如下所示的输出:
![set-as-startup-project](images/db-migrator-app.png)
#### 使用EF Core Update-Database命令
Ef Core具有`Update-Database`命令, 可根据需要创建数据库并应用挂起的迁移. 右键单击`.Web`项目并选择**设置为启动项目**:
![set-as-startup-project](images/set-as-startup-project.png)
打开**包管理器控制台(Package Manager Console)**, 选择`.EntityFrameworkCore.DbMigrations`项目作为**默认项目**并运行`Update-Database`命令:
![pcm-update-database](images/pcm-update-database-v2.png)
这将基于配置的连接字符串创建新数据库.
> 使用`.Migrator`工具是建议的方法, 因为它还能初始化初始数据能够正确运行Web应用程序.
### 运行应用程序
你现在可以运行应用程序,它将会打开**home**页面:
![bookstore-homepage](images/bookstore-homepage.png)
单击 **登录** 按钮, 输入用户名`admin`, 密码`1q2w3E*`, 登录应用程序.
启动模板包括**身份管理**和**租户管理**模块. 登录后,将显示"管理"菜单, 你可以在其中管理**租户**,**角色**,**用户**和**权限**. 用户管理页面如下所示:
![bookstore-user-management](images/bookstore-user-management-v2.png)
### 下一步是什么?
* [应用程序开发教程](Tutorials/AspNetCore-Mvc/Part-I.md)

8
docs/zh-Hans/Getting-Started-With-Startup-Templates.md

@ -1,8 +1,6 @@
# 启动模板入门
请参阅以下教程,了解如何使用预构建的应用程序启动模板开始使用ABP框架:
参阅下面的教程来学习如何开始使用的ABP框架预构建的应用程序启动模板:
* [ASP.NET Core MVC / Razor Pages UI 入门](Getting-Started?UI=MVC&DB=EF&Tiered=No)
* [Angular UI 入门](Getting-Started?UI=NG&DB=EF&Tiered=No)
<!-- TODO: this document has been moved, it should be deleted in the future. -->
* [ASP.NET Core MVC/Razor页面模板入门](Getting-Started-AspNetCore-MVC-Template.md)
* [Angular UI模板入门](Getting-Started-Angular-Template.md)

416
docs/zh-Hans/Getting-Started.md

@ -1,416 +0,0 @@
## 入门
````json
//[doc-params]
{
"UI": ["MVC","NG"],
"DB": ["EF", "Mongo"],
"Tiered": ["Yes", "No"]
}
````
本教程介绍了如何创建一个新的{{if UI == "MVC"}} ASP.NET Core MVC web {{else if UI == "NG"}} Angular {{end}}. 配置并运行它.
## 设置你的开发环境
创建第一个项目之前,需要正确的设置你的开发环境.
### 预先要求
你需要安装以下工具:
* [Visual Studio 2019 (v16.4+)](https://visualstudio.microsoft.com/vs/) for Windows / [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/).
* [.NET Core 3.0+](https://www.microsoft.com/net/download/dotnet-core/)
* [Node v12+](https://nodejs.org)
* [Yarn v1.19+](https://classic.yarnpkg.com/)
{{ if Tiered == "Yes" }}
* [Redis](https://redis.io/): 应用程序将Redis用作[分布式缓存](../Caching.md). 因此你需要安装并运行Redis.
{{ end }}
> 你可以也使用其他支持.NET Core 和 ASP.NET Core的编辑器.
### 安装ABP CLI
[ABP CLI](./CLI.md)是一个命令行页面,用于为基于ABP的应用程序验证和自动化一些任务.
> ABP CLI是[ABP框架](https://abp.io/)一个免费开源的工具.
你需要使用以下命令安排ABP CLI:
````shell
dotnet tool install -g Volo.Abp.Cli
````
如果你已经安装,你可以使用以下命令更新到最新版本:
````shell
dotnet tool update -g Volo.Abp.Cli
````
## 创建新项目
> 本文假设你使用 **{{ UI_Value }}** 做为UI框架 **{{ DB_Value }}** 做为数据库提供程序,对于其它选项,你可以更改文档顶部的首选项.
### 使用ABP CLI创建一个新项目
使用ABP CLI的 `new` 命令创建新项目:
````shell
abp new Acme.BookStore -t app{{if UI == "NG"}} -u angular {{end}}{{if DB == "Mongo"}} -d mongodb{{end}}{{if Tiered == "Yes" && UI != "NG"}} --tiered {{else if Tiered == "Yes" && UI == "NG"}}--separate-identity-server{{end}}
````
* `-t` 参数指定 [启动模板](Startup-Templates/Application.md) 名称. `app` 是一个启动模板名称,包含了预安装并且配置好的[ABP模块](Modules/Index.md).
{{ if UI == "NG" }}
* `-u` 指定UI框架, 本例中是 `angular`.
{{ if Tiered == "Yes" }}
* `--separate-identity-server` 参数用于将Identity服务器应用程序与API主机应用程序分隔开. 如果未指定,你将只有一个端点.
{{ end }}
{{ end }}
{{ if DB == "Mongo" }}
* `-d` 指定数据库提供程序, 本例中是 `mongodb`.
{{ end }}
{{ if Tiered == "Yes" && UI != "NG" }}
* `--tiered` 参数用于创建n层解决方案,其中身份验证服务器层,UI层和API层在物理上是分离的.
{{ end }}
> 你可以使用不同级别的命令空间; 例如. BookStore, Acme.BookStore or Acme.Retail.BookStore.
## 解决方案结构
{{ if UI == "MVC" }}
创建项目后你会有以下解决方案目录和文件:
![](images/solution-files-mvc.png)
在Visual Studio中打开 `.sln` 文件时,将看到以下解决方案结构:
{{if DB == "Mongo"}}
![vs-default-app-solution-structure](images/vs-app-solution-structure-mongodb.png)
{{else}}
![vs-default-app-solution-structure](images/vs-app-solution-structure{{if Tiered == "Yes"}}-tiered{{end}}.png)
{{end}}
{{ else if UI == "NG" }}
在创建的解决方案中有三个文件夹:
![](images/solution-files-non-mvc.png)
* `angular` 文件夹包含Angular UI应用程序.
* `aspnet-core` 文件夹包含后端应用程序.
* `react-native` 文件夹包含React Native UI 应用程序.
打开 `aspnet-core` 文件夹下的 `.sln`(`Visual Studio`解决方案)文件:
![vs-angular-app-backend-solution-structure](images/vs-spa-app-backend-structure{{if DB == "Mongo"}}-mongodb{{end}}.png)
{{ end }}
> ###### 关于解决方案中的项目
>
> 根据你的**UI**,**数据库**和其他选项,你的解决方案的结构可能略有不同.
该解决方案具有分层结构(基于[Domain Driven Design](Domain-Driven-Design.md)), 并包含配置好的的单元&集成测试项目.
{{ if DB == "EF" }}
集成测试项目已配置为可与 **EF Core** & **SQLite 内存** database同时使用.
{{ else if DB == "Mongo" }}
集成测试项目已配置为每个测试创建的内存中的**MongoDB**数据库(使用的[Mongo2Go](https://github.com/Mongo2Go/Mongo2Go)库).
{{ end }}
> 请参阅[应用程序模板文档](Startup-Templates/Application.md)详细了解解决方案结构.
## 创建数据库
### 数据库连接字符串
检查 {{if UI == "MVC"}}{{if Tiered == "Yes"}}`.IdentityServer` 和 `.HttpApi.Host` 项目{{else}}`.Web` 项目{{end}}{{else if UI == "NG" }}`.HttpApi.Host` 项目{{end}}下 `appsettings.json` 文件中的 **链接字符串**:
{{ if DB == "EF" }}
````json
"ConnectionStrings": {
"Default": "Server=localhost;Database=BookStore;Trusted_Connection=True"
}
````
该解决方案配置为**Entity Framework Core**与**MS SQL Server**一起使用. EF Core支持[各种](https://docs.microsoft.com/en-us/ef/core/providers/)数据库提供程序,因此你可以使用任何受支持的DBMS. 请参阅[Entity Framework集成文档](https://docs.abp.io/en/abp/latest/Entity-Framework-Core)了解如何切换到另一个DBMS.
### 数据库连接字符串
查看`.Web`项目下`appsettings.json`文件中的 **连接字符串**:
````json
{
"ConnectionStrings": {
"Default": "Server=localhost;Database=BookStore;Trusted_Connection=True"
}
}
````
解决方案使用 **Entity Framework Core****MS SQL Server**. EF Core支持[各种](https://docs.microsoft.com/zh-cn/ef/core/providers/)数据库提供程序,因此你可以根据实际需要使用其他DBMS. 如果需要,请更改连接字符串.
### 应用迁移
该解决方案使用[Entity Framework Core Code First 迁移](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/?tabs=dotnet-core-cli). 你需要应用迁移来创建数据库,有两种方法迁移数据库.
#### 使用DbMigrator应用程序应用迁移
该解决方案包含一个控制台应用程序(在此示例中名为`Acme.BookStore.DbMigrator`),可以创建数据库,应用迁移和初始化数据. 它对开发和生产环境都很有用.
> `.DbMigrator`项目有自己的`appsettings.json`. 因此,如果你更改了上面的连接字符串,则还应更改此字符串.
右键单击`.DbMigrator`项目并选择 **设置为启动项目**:
![set-as-startup-project](images/set-as-startup-project.png)
按F5(或Ctrl + F5)运行应用程序. 它将具有如下所示的输出:
![set-as-startup-project](images/db-migrator-app.png)
#### 使用EF Core Update-Database命令
Ef Core具有`Update-Database`命令, 可根据需要创建数据库并应用挂起的迁移. 右键单击`.Web`项目并选择**设置为启动项目**:
{{ if UI == "MVC" }}
右键单击{{if Tiered == "Yes"}}`.IdentityServer`{{else}}`.Web`{{end}}项目并选择**设置为启动项目**:
{{ else if UI != "MVC" }}
右键单击`.HttpApi.Host`项目并选择**设置为启动项目**:
{{ end }}
![set-as-startup-project](images/set-as-startup-project.png)
打开**包管理器控制台(Package Manager Console)**, 选择`.EntityFrameworkCore.DbMigrations`项目作为**默认项目**并运行`Update-Database`命令:
![package-manager-console-update-database](images/package-manager-console-update-database.png)
这将基于配置的连接字符串创建新数据库.
> 使用`.Migrator`工具是建议的方法, 因为它还能初始化初始数据能够正确运行Web应用程序.
{{ else if DB == "Mongo" }}
````json
"ConnectionStrings": {
"Default": "mongodb://localhost:27017/BookStore"
}
````
该解决方案被配置为在你的本地计算机中使用 **MongoDB**,因此你需要启动并运行一个MongoDB服务器实例或者将连接字符串更改为另一个MongoDB服务器.
### 初始化种子数据
该解决方案附带一个 `.DbMigrator` 控制台应用程序,该应用程序为初始数据提供了种子. 它对于开发以及生产环境都很有用.
> `.DbMigrator` 项目有自己的 `appsettings.json`.如果你更改了其他项目的 `appsettings.json`,也应该更改这个.
右键点击 `.DbMigrator` 并选择 **设置为启动项目**.
![set-as-startup-project](images/set-as-startup-project.png)
按F5(或Ctrl+F5)启动应用程序,你会看到以下输出:
![db-migrator-output](images/db-migrator-output.png)
> 数据库创建后会初始化种子数据, 其中包含用于登录的 `admin` 用户. 所以你至少使用 `.DbMigrator` 一次.
{{ end }}
### 运行应用程序
{{ if UI == "MVC" }}
{{ if Tiered == "Yes" }}
确保 `.IdentityServer` 是启动项目,运行应用程序后会在你的浏览器打开一个 **login** 页面.
> 在Visual Studio中使用Ctrl+F5(而不是F5)运行应用,如果你不用于调试,这会减少启动时间.
你可以登录,但是不能在这里进入主应用程序,它仅是验证服务器.
确保 `.HttpApi.Host` 是启动项目,运行应用程序后会在你的浏览器打开一个 **Swagger UI** 页面.
![swagger-ui](images/swagger-ui.png)
这里是Web应用程序使用的API应用程序.
最后确保 `.Web` 是启动项目,运行应用程序后会在你的浏览器打开一个 **welcome** 页面.
![mvc-tiered-app-home](images/bookstore-home.png)
点击 **login** 按钮重定向到 `Identity Server` 来登录应用程序.
![bookstore-login](images/bookstore-login.png)
{{ else }}
最后确保 `.Web` 是启动项目,运行应用程序后会在你的浏览器打开一个 **login** 页面.
> 在Visual Studio中使用Ctrl+F5(而不是F5)运行应用,如果你不用于调试,这会减少启动时间.
![bookstore-login](images/bookstore-login.png)
{{ end }}
{{ else if UI != "MVC" }}
#### 运行HTTP API Host (服务器端)
{{ if Tiered == "Yes" }}
确保 `.IdentityServer` 是启动项目,运行应用程序后会在你的浏览器打开一个 **login** 页面.
> 在Visual Studio中使用Ctrl+F5(而不是F5)运行应用,如果你不用于调试,这会减少启动时间.
你可以登录,但是不能在这里进入主应用程序,它仅是验证服务器.
{{ end }}
确保 `.HttpApi.Host` 是启动项目,运行应用程序后会在你的浏览器打开一个 **Swagger UI** 页面.
{{ if Tiered == "No" }}
> 在Visual Studio中使用Ctrl+F5(而不是F5)运行应用,如果你不用于调试,这会减少启动时间.
{{ end }}
![swagger-ui](images/swagger-ui.png)
你可以看到应用程序的API并进行测试. 更多信息,请参阅[Swagger UI](https://swagger.io/tools/swagger-ui/).
> ##### Swagger UI 授权
>
> 大多数的HTTP API都需要身份验证和授权. 如果你要测试授权API, 请手动进入 `/Account/Login` 页面, 输入用户名: `admin` 和密码: `1q2w3E*` 登录到应用程序. 然后你可以访问授权API.
{{ end }}
{{ if UI == "NG" }}
#### 运行 Angular 应用程序 (客户端)
`angular` 下打开命令行终端, 输入 `yarn` 命令(我们推荐使用[yarn](https://yarnpkg.com/)包管理, `npm install` 在大多数情况下也可以工作).
```bash
yarn
```
等到所有node模块加载成功, 执行 `yarn start` (或 `npm start`) 命令:
```bash
yarn start
```
等待 `Angular CLI` 使用 `BrowserSync` 启动 `Webpack` dev-server.
它会负责编译你的 `TypeScript`代码, 并自动重新加载浏览器.
完成后 `Angular Live Development Server` 会监听 localhost:4200.
打开你的浏览器并导航到[localhost:4200](http://localhost:4200/).
![bookstore-login](images/bookstore-login.png)
{{ end }}
输入用户名 **admin**,密码 **1q2w3E*** 登录到应用程序.
![bookstore-home](images/bookstore-home.png)
应用程序已经启动并执行,你可以基于该启动模板开发应用程序.
#### 移动开发
ABP平台提供了[React Native](https://reactnative.dev/)模板用于开发移动应用程序.
> 该解决方案默认 `react-native` 包含了React Native应用程序,如果你不计划使用React Native开发移动应用程序,你可以忽略并删除 `react-native` 文件夹.
运行在Android模拟器或真机上的React Native应用程序无法连接到 `localhost` 上的后.要修复此问题,需要在本地IP上运行后端.
{{ if Tiered == "No"}}
![React Native host project local IP entry](images/rn-host-local-ip.png)
* 打开 `.HttpApi.Host` 文件夹下的 `appsettings.json` 文件. 将 `SelfUrl``Authority` 属性的 `localhost` 替换为你本地的IP地址.
* 打开 `.HttpApi.Host/Properties` 文件夹下的 `launchSettings.json` 文件. 将 `applicationUrl` 属性的 `localhost` 替换为你本地的IP地址.
{{ else if Tiered == "Yes" }}
![React Native tiered project local IP entry](images/rn-tiered-local-ip.png)
* 打开 `.IdentityServer` 文件夹下的 `appsettings.json` 文件. 将 `SelfUrl` 属性的 `localhost` 替换为你本地的IP地址.
* 打开 `.IdentityServer/Properties` 文件夹下的 `launchSettings.json` 文件. 将 `applicationUrl` 属性的 `localhost` 替换为你本地的IP地址.
* 打开 `.HttpApi.Host` 文件夹下的 `appsettings.json` 文件. 将 `Authority` 属性的 `localhost` 替换为你本地的IP地址.
* 打开 `.HttpApi.Host/Properties` 文件夹下的 `launchSettings.json` 文件. 将 `applicationUrl` 属性的 `localhost` 替换为你本地的IP地址.
{{ end }}
按照**运行HTTP API Host (服务端口)**那样运行后端.
> React Native应用程序不信任自动生成的.NET HTTPS证书,你可以在开发期间使用HTTP.
`react-native` 文件夹打开命令行终端,输入 `yarn` 命令(我们推荐使用[yarn](https://yarnpkg.com/)包管理, `npm install` 在大多数情况下也可以工作).
```bash
yarn
```
* 打开 `react-nativer` 文件夹下的 `Environment.js` 文件. 将 `apiUrl``issuer` 属性的 `localhost` 替换为你本地的IP地址:
![react native environment local IP](images/rn-environment-local-ip.png)
{{ if Tiered == "Yes" }}
> 确保 `issuer` 与正在运行的 `.IdentityServer` 项目匹配, `apiUrl` 与正在运行的 `.HttpApi.Host` 项目匹配.
{{else}}
> 确保 `issuer``apiUrl` 与正在运行的 `.HttpApi.Host` 项目匹配
{{ end }}
等到所有node模块加载成功, 执行 `yarn start` (或 `npm start`) 命令:
```bash
yarn start
```
等待Expo CLI启动后Expo CLI在 `http://localhost:19002/` 地址要开管理页面.
![expo-interface](images/rn-expo-interface.png)
在上面的管理界面中,可以通过使用[Expo Client](https://expo.io/tools#client)扫描二维码,使用Android模拟器,iOS模拟器或真机来启动应用程序.
> 请参阅[expo.io](https://docs.expo.io/versions/v36.0.0/workflow/ios-simulator/)上的[Android Studio模拟器](https://docs.expo.io/versions/v36.0.0/workflow/android-studio-emulator/)和[iOS模拟器文档](https://docs.expo.io/versions/v36.0.0/workflow/android-studio-emulator/).
![React Native login screen on iPhone 11](images/rn-login-iphone.png)
输入用户名 **admin**,密码 **1q2w3E*** 登录到应用程序.
应用程序已经启动并执行,你可以基于该启动模板开发应用程序.
> [应用程序启动模板](Startup-Templates/Application.md) 包含租户管理和Identity模块.
## 下一步是什么?
[应用程序开发教程](Tutorials/Part-1.md)

2
docs/zh-Hans/Index.md

@ -12,7 +12,7 @@ ABP是一个**开源应用程序框架**,专注于基于ASP.NET Core的Web应用
* [ASP.NET Core MVC 模板](Getting-Started-AspNetCore-MVC-Template.md)
如果想从头开始(使用空项目),请手动安装ABP框架并使用以下教程:
如果想从头开始(使用空项目),请手动安装ABP框架并使用以下教程:
* [控制台应用程序](Getting-Started-Console-Application.md)
* [ASP.NET Core Web 应用程序](Getting-Started-AspNetCore-Application.md)

52
docs/zh-Hans/Localization.md

@ -86,21 +86,6 @@ JSON文件位于 "/Localization/Resources/Test" 项目文件夹下, 如下图所
* 每个本地化文件都需要定义 `culture` (文化) 代码 (例如 "en" 或 "en-US").
* `texts` 部分只包含本地化字符串的键值集合 (键也可能有空格).
### 默认资源
可以将 `AbpLocalizationOptions.DefaultResourceType` 设置为资源类型,在未指定本地化资源时使用:
````csharp
Configure<AbpLocalizationOptions>(options =>
{
options.DefaultResourceType = typeof(TestResource);
});
````
> [启动模板]](Startup-Templates/Application.md) 设置 `DefaultResourceType` 为应用程序的本地化资源.
请参阅下面的*客户端*部分获取用例
##### 简短的本地化资源名称
本地化资源也可以在客户端(JavaScript)使用. 因此, 为本地化资源设置一个简短的名称可以更方便的本地化文本. 例如:
@ -180,10 +165,6 @@ public class MyService
}
````
##### 格式参数
格式参数可以在本地化Key参数后传递,如果你的消息是 `Hello {0}, welcome!`,可以将 `{0}` 传递给localizer,例如: `_localizer["HelloMessage", "John"]`.
###### 在Razor视图/Page中简单的用法
````c#
@ -198,9 +179,7 @@ public class MyService
ABP提供了JavaScript服务, 可以在客户端使用相同的本地化文本.
#### getResource
`abp.localization.getResource` 函数用于获取本地化资源:
获取本地化资源:
````js
var testResource = abp.localization.getResource('Test');
@ -212,33 +191,6 @@ var testResource = abp.localization.getResource('Test');
var str = testResource('HelloWorld');
````
#### 本地化
`abp.localization.localize` 函数用于获取本地化文本,你可以传递本地化Key和资源名称:
````js
var str = abp.localization.localize('HelloWorld', 'Test');
````
`HelloWorld` 是本地化文本的Key, `Test` 是本地化资源的名称.
如果未指定本地化资源名称,它使用 `AbpLocalizationOptions` 中定义的默认本地化资源(参见上面的*默认资源*部分). 例:
````js
var str = abp.localization.localize('HelloWorld'); //uses the default resource
````
##### 格式参数
如果本地化字符串包含参数, 例如 `Hello {0}, welcome!`. 你可以将参数传递给本地化方法. 例:
````js
var str1 = abp.localization.getResource('Test')('HelloWelcomeMessage', 'John');
var str2 = abp.localization.localize('HelloWorld', 'Test', 'John');
````
上面的两个示例都会输出 `Hello John, welcome!`.
## 另请参阅
## See Also
* [Angular UI中的本地化](UI/Angular/Localization.md)

12
docs/zh-Hans/Modules/Docs.md

@ -326,7 +326,7 @@ There are no projects yet!
{"GitHubRootUrl":"https://github.com/abpframework/abp/tree/{version}/docs/zh-Hans/","GitHubAccessToken":"***","GitHubUserAgent":""}
```
注意 `GitHubAccessToken``***` 掩盖. 这是一个私人令牌,你必须从GitHub获取它. 请参阅 https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/
注意 `GitHubAccessToken``***` 掩盖. 这是一个私人令牌你必须从GitHub获取它. 请参阅 https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/
- MainWebsiteUrl: `/`
@ -338,7 +338,7 @@ There are no projects yet!
INSERT [dbo].[DocsProjects] ([Id], [Name], [ShortName], [Format], [DefaultDocumentName], [NavigationDocumentName], [MinimumVersion], [DocumentStoreType], [ExtraProperties], [MainWebsiteUrl], [LatestVersionBranchName], [ParametersDocumentName]) VALUES (N'12f21123-e08e-4f15-bedb-ae0b2d939658', N'ABP framework (GitHub)', N'abp', N'md', N'Index', N'docs-nav.json', NULL, N'GitHub', N'{"GitHubRootUrl":"https://github.com/abpframework/abp/tree/{version}/docs","GitHubAccessToken":"***","GitHubUserAgent":""}', N'/', N'master', N'')
```
请注意,`GitHubAccessToken` 被屏蔽了.它是一个私人令牌,你必须获得自己的令牌并替换 `***` 字符串.
请注意`GitHubAccessToken` 被屏蔽了.它是一个私人令牌,你必须获得自己的令牌并替换 `***` 字符串.
现在你可以运行应用程序并导航到 `/Documents`.
@ -378,9 +378,9 @@ INSERT [dbo].[DocsProjects] ([Id], [Name], [ShortName], [Format], [DefaultDocume
INSERT [dbo].[DocsProjects] ([Id], [Name], [ShortName], [Format], [DefaultDocumentName], [NavigationDocumentName], [MinimumVersion], [DocumentStoreType], [ExtraProperties], [MainWebsiteUrl], [LatestVersionBranchName], [ParametersDocumentName]) VALUES (N'12f21123-e08e-4f15-bedb-ae0b2d939659', N'ABP framework (FileSystem)', N'abp', N'md', N'Index', N'docs-nav.json', NULL, N'FileSystem', N'{"Path":"C:\\Github\\abp\\docs"}', N'/', NULL, N'')
```
添加上面的一个示例项目后运行该应用程序. 在菜单中你会看到`文档` 链接,点击菜单链接打开文档页面.
添加上面的一个示例项目后运行该应用程序. 在菜单中你会看到`文档` 链接点击菜单链接打开文档页面.
到目前为止, 我们已经从abp.io网站创建了一个新的应用程序,并为Docs模块做好准备.
到目前为止, 我们已经从abp.io网站创建了一个新的应用程序并为Docs模块做好准备.
### 7- 添加一个新文档
@ -454,7 +454,7 @@ public class Person
}
```
因为并不是项目中的每个文档都有章节或者不需要所有的参数,你必须声明哪些参数将用于对文档进行分段,在文档的任何地方都可以使用JSON块.
因为并不是项目中的每个文档都有章节或者不需要所有的参数,你必须声明哪些参数将用于对文档进行分段在文档的任何地方都可以使用JSON块.
例如 [Getting-Started.md](https://github.com/abpio/abp-commercial-docs/blob/master/en/getting-started.md):
@ -570,7 +570,7 @@ This document assumes that you prefer to use **{{ UI_Value }}** as the UI framew
![Navigation menu](../images/docs-module_download-sample-navigation-menu.png)
最后,为你的项目添加了一个新的Docs模块, 该模块由GitHub提供.
最后,为您的项目添加了一个新的Docs模块, 该模块由GitHub提供.
## 全文搜索(Elastic Search)

108
docs/zh-Hans/Object-Extensions.md

@ -62,7 +62,7 @@ if (user.GetProperty<bool>("IsSuperUser"))
##### 非基本属性类型
如果你的属性类型不是原始类型(int,bool,枚举,字符串等),你需要使用 `GetProperty` 的非泛型版本,它会返回 `object`.
如果您的属性类型不是原始类型(int,bool,枚举,字符串等),你需要使用 `GetProperty` 的非泛型版本,它会返回 `object`.
#### HasProperty
@ -176,111 +176,11 @@ ObjectExtensionManager.Instance
`options` 有一个名为 `Configuration` 的字典,该字典存储对象扩展定义甚至可以扩展. EF Core使用它来将其他属性映射到数据库中的表字段. 请参阅[扩展实体文档](Customizing-Application-Modules-Extending-Entities.md).
#### CheckPairDefinitionOnMapping
控制在映射两个可扩展对象时如何检查属性定义. 请参阅*对象到对象映射*部分,了解 `CheckPairDefinitionOnMapping` 选项.
## Validation
你可能要为你定义的额外属性添加一些 **验证规则**. `AddOrUpdateProperty` 方法选项允许进行验证的方法有两种:
1. 你可以为属性添加 **数据注解 attributes**.
2. 你可以给定一个action(代码块)执行 **自定义验证**.
当你在**自动验证**的方法(例如:控制器操作,页面处理程序方法,应用程序服务方法...)中使用对象时,验证会工作. 因此,每当扩展对象被验证时,所有额外的属性都会被验证.
### 数据注解 Attributes
所有标准的数据注解Attributes对于额外属性都是有效的. 例:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUserCreateDto, string>(
"SocialSecurityNumber",
options =>
{
options.Attributes.Add(new RequiredAttribute());
options.Attributes.Add(
new StringLengthAttribute(32) {
MinimumLength = 6
}
);
});
````
使用以上配置,如果没有提供有效的 `SocialSecurityNumber` 值, `IdentityUserCreateDto` 对象将是无效的.
### 自定义验证
如果需要,可以添加一个自定义action验证额外属性. 例:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUserCreateDto, string>(
"SocialSecurityNumber",
options =>
{
options.Validators.Add(context =>
{
var socialSecurityNumber = context.Value as string;
if (socialSecurityNumber == null ||
socialSecurityNumber.StartsWith("X"))
{
context.ValidationErrors.Add(
new ValidationResult(
"Invalid social security number: " + socialSecurityNumber,
new[] { "SocialSecurityNumber" }
)
);
}
});
});
````
`context.ServiceProvider` 可以解析服务.
除了为单个属性添加自定义验证逻辑外,还可以添加在对象级执行的自定义验证逻辑. 例:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdate<IdentityUserCreateDto>(objConfig =>
{
//Define two properties with their own validation rules
objConfig.AddOrUpdateProperty<string>("Password", propertyConfig =>
{
propertyConfig.Attributes.Add(new RequiredAttribute());
});
objConfig.AddOrUpdateProperty<string>("PasswordRepeat", propertyConfig =>
{
propertyConfig.Attributes.Add(new RequiredAttribute());
});
//Write a common validation logic works on multiple properties
objConfig.Validators.Add(context =>
{
if (context.ValidatingObject.GetProperty<string>("Password") !=
context.ValidatingObject.GetProperty<string>("PasswordRepeat"))
{
context.ValidationErrors.Add(
new ValidationResult(
"Please repeat the same password!",
new[] { "Password", "PasswordRepeat" }
)
);
}
});
});
````
## 对象到对象映射
假设你已向可扩展的实体对象添加了额外的属性并使用了自动[对象到对象的映射](Object-To-Object-Mapping.md)将该实体映射到可扩展的DTO类. 在这种情况下你需要格外小心,因为额外属性可能包含**敏感数据**,这些数据对于客户端不可用.
本节提供了一些**好的做法**,可以控制对象映射的额外属性.
本节提供了一些**好的做法**,可以控制对象映射的额外属性。
### MapExtraPropertiesTo
@ -332,7 +232,7 @@ identityUser.MapExtraPropertiesTo(
#### AutoMapper集成
如果使用的是[AutoMapper](https://automapper.org/)库,ABP框架还提供了一种扩展方法来利用上面定义的 `MapExtraPropertiesTo` 方法.
如果使用的是[AutoMapper](https://automapper.org/)库,ABP框架还提供了一种扩展方法来利用上面定义的 `MapExtraPropertiesTo` 方法.
你可以在映射配置文件中使用 `MapExtraProperties()` 方法.
@ -347,7 +247,7 @@ public class MyProfile : Profile
}
````
它与 `MapExtraPropertiesTo()` 方法具有相同的参数.
它与 `MapExtraPropertiesTo()` 方法具有相同的参数
## Entity Framework Core 数据库映射

4
docs/zh-Hans/Object-To-Object-Mapping.md

@ -168,7 +168,7 @@ public class MyProfile : Profile
假设你已经创建了一个**可重用的模块**,其中定义了AutoMapper配置文件,并在需要映射对象时使用 `IObjectMapper`. 根据[模块化](Module-Development-Basics.md)的性质,你的模块可以用于不同的应用程序.
`IObjectMapper` 是一个抽象,可以由最终应用程序替换使用另一个映射库. 这里的问题是你的可重用模块设计为使用AutoMapper,因为它为其定义映射配置文件. 这种情况下即使最终应用程序使用另一个默认对象映射库,你也要保证模块始终使用AutoMapper.
`IObjectMapper` 是一个抽象,可以由最终应用程序替换使用另一个映射库. 这里的问题是你的可重用模块设计为使用AutoMapper因为它为其定义映射配置文件. 这种情况下即使最终应用程序使用另一个默认对象映射库,你也要保证模块始终使用AutoMapper.
`IObjectMapper<TContext>`将对象映射器上下文化,你可以为不同的 模块/上下文 使用不同的库.
@ -201,7 +201,7 @@ public class UserAppService : ApplicationService
`UserAppService` 注入 `IObjectMapper<MyModule>`, 它是模块的特定对象映射器,用法与 `IObjectMapper` 完全相同.
上面的示例代码未使用 `ApplicationService` 中定义的 `ObjectMapper` 属性,而是注入了 `IObjectMapper<MyModule>`. 但是 `ApplicationService` 定义了可以在类构造函数中设置的 `ObjectMapperContext` 属性, 因此仍然可以使用基类属性. 示例可以进行以下重写:
上面的示例代码未使用 `ApplicationService` 中定义的 `ObjectMapper` 属性而是注入了 `IObjectMapper<MyModule>`. 但是 `ApplicationService` 定义了可以在类构造函数中设置的 `ObjectMapperContext` 属性, 因此仍然可以使用基类属性. 示例可以进行以下重写:
````csharp
public class UserAppService : ApplicationService

10
docs/zh-Hans/Options.md

@ -9,7 +9,7 @@ ABP框架遵循选项模式,并定义了用于配置框架和模块的选项类(
## 配置选项
通常配置选项在 `Startup` 类的 `ConfigureServices` 方法中. 但由于ABP框架提供了模块化基础设施,因此你可以在[模块](Module-Development-Basics.md)的`ConfigureServices` 方法配置选项.
:
````csharp
public override void ConfigureServices(ServiceConfigurationContext context)
@ -34,7 +34,7 @@ public class MyOptions
}
````
然后开发人员可以像上面 `AbpAuditingOptions` 示例一样配置你的选项:
然后开发人员可以像上面 `AbpAuditingOptions` 示例一样配置你的选项
````csharp
public override void ConfigureServices(ServiceConfigurationContext context)
@ -80,9 +80,9 @@ public class MyService : ITransientDependency
如果你正在开发一个模块,可能需要让开发者能够设置一些选项,并在依赖注入注册阶段使用这些选项. 你可能需要根据选项值配置其他服务或更改依赖注入的注册代码.
对于此类情况,ABP为 `IServiceCollection` 引入了 `PreConfigure<TOptions>``ExecutePreConfiguredActions<TOptions>` 扩展方法. 该模式的工作原理如下所述.
对于此类情况,ABP为 `IServiceCollection` 引入了 `PreConfigure<TOptions>``ExecutePreConfiguredActions<TOptions>` 扩展方法. 该模式的工作原理如下所述
1. 你的模块中定义计划选项类. 例:
1. 你的模块中定义计划选项类. 例
````csharp
public class MyPreOptions
@ -92,7 +92,7 @@ public class MyPreOptions
````
然后任何依赖于模块的模块类都可以在其 `PreConfigureServices` 方法中使用 `PreConfigure<TOptions>` 方法.
:
````csharp
public override void PreConfigureServices(ServiceConfigurationContext context)

24
docs/zh-Hans/Samples/Microservice-Demo.md

@ -51,13 +51,13 @@ ABP框架的主要目标之一就是提供[便捷的基础设施来创建微服
### 创建数据库
MongoDB 数据库是动态创建的,但是你需要创建 SQL server 数据库的结构.其实你可以很轻松的创建数据库,因为这个解决方案配置了使用 Entity Core Code First 来做迁移.
MongoDB 数据库是动态创建的,但是你需要创建 SQL server 数据库的结构。其实你可以很轻松的创建数据库,因为这个解决方案配置了使用 Entity Core Code First 来做迁移。
这个解决方案中有两个 SQL server 数据库.
这个解决方案中有两个 SQL server 数据库
#### MsDemo_Identity 数据库
* 右键 `AuthServer.Host` 项目,然后点击 `设置为启动项目`.
* 右键 `AuthServer.Host` 项目然后点击 `设置为启动项目`.
* 打开 **程序包管理器控制台** (工具 -> NuGet 包管理器 -> 程序包管理器控制台)
* 选择 `AuthServer.Host` 成为 **默认项目**.
* 执行 `Update-Database` 命令.
@ -66,7 +66,7 @@ MongoDB 数据库是动态创建的,但是你需要创建 SQL server 数据库
#### MsDemo_ProductManagement
* 右键 `ProductService.Host` 项目,然后点击 `设置为启动项目`.
* 右键 `ProductService.Host` 项目然后点击 `设置为启动项目`.
* 打开 **程序包管理器控制台** (工具 -> NuGet 包管理器 -> 程序包管理器控制台)
* 选择 `ProductService.Host` 成为 **默认项目**.
* 执行 `Update-Database` 命令.
@ -334,7 +334,7 @@ context.Services.AddAuthentication(options =>
- 它需要额外的身份范围 *role*, *email* and *phone*.
- 它需要API资源范围 *PublicWebSiteGateway*,*BloggingService*和*ProductService*,因为它将这些服务用作API.
IdentityServer客户端设置存储在`appsettings.json`文件中:
IdentityServer客户端设置存储在`appsettings.json`文件中
```json
"AuthServer": {
@ -348,7 +348,7 @@ IdentityServer客户端设置存储在`appsettings.json`文件中:
PublicWebSite.Host项目有一个列出产品的页面 (`Pages/Products.cshtml`). 它还使用博客模块中的UI. 为此`PublicWebSiteHostModule`加入了`BloggingWebModule`(*[Volo.Blogging.Web](https://www.nuget.org/packages/Volo.Blogging.Web)* 包)的依赖项.
产品页面的屏幕截图:
产品页面的屏幕截图
![microservice-sample-public-product-list](../images/microservice-sample-public-product-list.png)
@ -390,7 +390,7 @@ PublicWebSite.Host项目有一个列出产品的页面 (`Pages/Products.cshtml`)
#### 远程服务配置
`appsettings.json`文件中的`RemoteService`配置很简单:
`appsettings.json`文件中的`RemoteService`配置很简单
````json
"RemoteServices": {
@ -418,7 +418,7 @@ PublicWebSite.Host项目有一个列出产品的页面 (`Pages/Products.cshtml`)
}
````
此示例使用`client_credentials` 授予类型,该类型需要`ClientId`和`ClientSecret`进行身份验证过程. 还有[其他授予类型](http://docs.identityserver.io/en/latest/topics/grant_types.html). 例如, 你可以使用以下配置切换到`password`(Resource Owner Password)授予类型:
此示例使用`client_credentials` 授予类型,该类型需要`ClientId`和`ClientSecret`进行身份验证过程. 还有[其他授予类型](http://docs.identityserver.io/en/latest/topics/grant_types.html). 例如, 你可以使用以下配置切换到`password`(Resource Owner Password)授予类型
````json
"IdentityClients": {
@ -571,7 +571,7 @@ app.UseOcelot().Wait();
#### 权限管理
后端管理应用程序提供权限管理UI(之前见过),并使用此网关获取/设置权限. 权限管理API托管在网关内,而不是单独的服务. 这是一个设计决策,但如果愿意,它可以作为另一个微服务托管.
后端管理应用程序提供权限管理UI(之前见过),并使用此网关获取/设置权限. 权限管理API托管在网关内,而不是单独的服务. 这是一个设计决策,但如果愿意,它可以作为另一个微服务托管.
#### Dependencies
@ -1229,7 +1229,7 @@ public class ProductCodeAlreadyExistsException : BusinessException
}
````
`PM:000001`是发送给客户端的异常类型的代码,因此他们可以理解错误类型. 在这种情况下没有实现,但也可以本地化业务异常. 请参阅[异常处理文档](../Exception-Handling.md).
`PM000001`是发送给客户端的异常类型的代码,因此他们可以理解错误类型. 在这种情况下没有实现,但也可以本地化业务异常. 请参阅[异常处理文档](../Exception-Handling.md).
#### 应用层
@ -1278,7 +1278,7 @@ public async Task<ProductDto> UpdateAsync(Guid id, UpdateProductDto input)
分布式事件(事件总线)是一种消息传递方式,其中服务引发/触发事件,而其他服务注册/侦听这些事件,以便在发生重要事件时得到通知. ABP通过提供约定,服务和集成使分布式事件更易于使用.
已经看到`Product`类使用以下代码行发布事件:
已经看到`Product`类使用以下代码行发布事件:
````csharp
AddDistributedEvent(new ProductStockCountChangedEto(Id, StockCount, stockCount));
@ -1406,7 +1406,7 @@ Kibana URL默认为`http://localhost:5601/`.
ABP提供自动审计日志记录,详细保存每个请求(当前用户,浏览器/客户端,执行了哪些操作,哪些实体更改,甚至实体的哪些属性已更新). 有关详细信息,请参阅[审计日志文档](../Audit-Logging.md).
所有服务和应用程序都配置为编写审核日志. 审核日志将保存到MsDemo_Identity SQL数据库中. 因此,可以从单个点查询所有应用程序的所有审核日志.
所有服务和应用程序都配置为编写审核日志. 审核日志将保存到MsDemo_Identity SQL数据库中. 因此,可以从单个点查询所有应用程序的所有审核日志.
审核日志记录具有`CorrelationId`属性,可用于跟踪请求. 当服务在单个Web请求中调用另一个服务时,它们都会使用相同的`CorrelationId`保存审核日志. 请参阅数据库中的`AbpAuditLogs`表.

2
docs/zh-Hans/Settings.md

@ -226,6 +226,6 @@ Configure<AbpSettingOptions>(options =>
## 设置管理模块
设置系统核心是相当独立的,不做任何关于如何管理(更改)设置值的假设. 默认的`ISettingStore`实现也是`NullSettingStore`,它为所有设置值返回null.
设置系统核心是相当独立的,不做任何关于如何管理(更改)设置值的假设. 默认的`ISettingStore`实现也是`NullSettingStore`它为所有设置值返回null.
设置管理模块通过管理数据库中的设置值来完成逻辑(实现`ISettingStore`).有关更多信息参阅[设置管理模块](Modules/Setting-Management.md)学习更多.

3
docs/zh-Hans/Specifications.md

@ -1,3 +0,0 @@
## 规约
TODO..

4
docs/zh-Hans/Startup-Templates/Application.md

@ -348,7 +348,7 @@ Home模块是一个可延迟加载的模块, 它加载应用程序的根地址.
React 本机应用程序是用 [Expo](https://expo.io/)生成的. Expo 是一套基于 React Native 构建的工具, 帮助你快速启动一个应用程序, 尽管它有很多功能.
React Native 应用文件夹结构, 如下图所示:
React Native 应用文件夹结构, 如下图所示
![react-native-folder-structure](../images/react-native-folder-structure.png)
@ -380,7 +380,7 @@ Screens 是通过在 `src/screens` 文件夹中创建将名称分开的文件夹
[Redux](https://redux.js.org/) 被用作状态管理库. [Redux Toolkit](https://redux-toolkit.js.org/) 库被用作高效Redux开发的工具集.
`src/store` 文件夹中创建 Actions, reducers, sagas, selectors. 存储文件夹如下:
`src/store` 文件夹中创建 Actions, reducers, sagas, selectors. 存储文件夹如下
![react-native-store-folder](../images/react-native-store-folder.png)

2
docs/zh-Hans/Startup-Templates/Index.md

@ -2,7 +2,7 @@
虽然你可以从一个空项目开始并手动添加所需的包,但启动模板可以非常轻松,舒适地使用ABP框架启动新的解决方案.
单击下面列表中的名称以查看相关启动模板的文档:
单击下面列表中的名称以查看相关启动模板的文档
* [**app**](Application.md): 应用程序模板.
* [**module**](Module.md): 模块/服务模板.

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

@ -33,7 +33,7 @@
- `Acme.BookStore.Domain`包含你的[实体](https://docs.abp.io/zh-Hans/abp/latest/Entities), [领域服务](https://docs.abp.io/zh-Hans/abp/latest/Domain-Services)和其他核心域对象.
- `Acme.BookStore.Domain.Shared`包含可与客户共享的常量,枚举或其他域相关对象.
在解决方案的**领域层**(`Acme.BookStore.Domain`项目)中定义[实体](https://docs.abp.io/zh-Hans/abp/latest/Entities). 该应用程序的主要实体是`Book`. 在`Acme.BookStore.Domain`项目中创建一个名为`Book`的类,如下所示:
在解决方案的**领域层**(`Acme.BookStore.Domain`项目)中定义[实体](https://docs.abp.io/zh-Hans/abp/latest/Entities). 该应用程序的主要实体是`Book`. 在`Acme.BookStore.Domain`项目中创建一个名为`Book`的类,如下所示:
````C#
using System;
@ -132,7 +132,7 @@ PM> Update-Database
#### 添加示例数据
`Update-Database`命令在数据库中创建了`AppBooks`表. 打开数据库并输入几个示例行,以便在页面上显示它们:
`Update-Database`命令在数据库中创建了`AppBooks`表. 打开数据库并输入几个示例行以便在页面上显示它们:
![bookstore-books-table](images/bookstore-books-table.png)
@ -290,7 +290,7 @@ namespace Acme.BookStore
#### Swagger UI
启动模板配置为使用[Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore)运行[swagger UI](https://swagger.io/tools/swagger-ui/). 运行应用程序并在浏览器中输入`https://localhost:XXXX/swagger/`(用自己的端口替换XXXX)作为URL.
启动模板配置为使用[Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore)运行[swagger UI](https://swagger.io/tools/swagger-ui/). 运行应用程序并在浏览器中输入`https://localhost:XXXX/swagger/`(用自己的端口替换XXXX)作为URL.
你会看到一些内置的接口和`Book`的接口,它们都是REST风格的:
@ -380,7 +380,7 @@ context.Menu.AddItem(
![bookstore-localization-files](images/bookstore-localization-files-v2.png)
打开`en.json`文件,将`Menu:BookStore`和`Menu:Books`键的本地化文本添加到文件末尾:
打开`en.json`文件,将`Menu:BookStore`和`Menu:Books`键的本地化文本添加到文件末尾:
````json
{

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

@ -14,7 +14,7 @@
### 解决方案中的测试项目
解决方案中有多个测试项目:
解决方案中有多个测试项目
![bookstore-test-projects-v2](images/bookstore-test-projects-v2.png)
@ -68,7 +68,7 @@ namespace Acme.BookStore
````
* 注入`IRepository<Book,Guid>`并在`SeedAsync`中使用它来创建两个书实体作为测试数据.
* 使用`IGuidGenerator`服务创建GUID. 虽然`Guid.NewGuid()`非常适合测试,但`IGuidGenerator`在使用真实数据库时还有其他特别重要的功能(参见[Guid生成文档](../../../Guid-Generation.md)了解更多信息).
* 使用`IGuidGenerator`服务创建GUID. 虽然`Guid.NewGuid()`非常适合测试但`IGuidGenerator`在使用真实数据库时还有其他特别重要的功能(参见[Guid生成文档](../../../Guid-Generation.md)了解更多信息).
### 测试 BookAppService

0
docs/zh-Hans/Tutorials/images/bookstore-add-create-dialog-v2.png → docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-add-create-dialog-v2.png

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

0
docs/zh-Hans/Tutorials/images/bookstore-add-edit-dialog.png → docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-add-edit-dialog.png

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

0
docs/zh-Hans/Tutorials/images/bookstore-add-index-page-v2.png → docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-add-index-page-v2.png

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

0
docs/zh-Hans/Tutorials/images/bookstore-appservice-tests.png → docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-appservice-tests.png

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

BIN
docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-book-list-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

0
docs/zh-Hans/Tutorials/images/bookstore-books-table-actions.png → docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-books-table-actions.png

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

0
docs/zh-Hans/Tutorials/images/bookstore-books-table.png → docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-books-table.png

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

0
docs/zh-Hans/Tutorials/images/bookstore-create-dialog-2.png → docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-create-dialog-2.png

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

0
docs/zh-Hans/Tutorials/images/bookstore-create-dialog.png → docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-create-dialog.png

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

BIN
docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-create-template.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

0
docs/zh-Hans/Tutorials/images/bookstore-homepage.png → docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-homepage.png

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

0
docs/zh-Hans/Tutorials/images/bookstore-index-js-file-v2.png → docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-index-js-file-v2.png

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-localization-files-v2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

0
docs/zh-Hans/Tutorials/images/bookstore-menu-items.png → docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-menu-items.png

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

BIN
docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-new-book-button.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-pmc-add-book-migration-v2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

0
docs/zh-Hans/Tutorials/images/bookstore-pmc-add-book-migration.png → docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-pmc-add-book-migration.png

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

BIN
docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-swagger.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

0
docs/zh-Hans/Tutorials/images/bookstore-test-js-proxy-getlist-network.png → docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-test-js-proxy-getlist-network.png

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

0
docs/zh-Hans/Tutorials/images/bookstore-test-js-proxy-getlist.png → docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-test-js-proxy-getlist.png

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

0
docs/zh-Hans/Tutorials/images/bookstore-test-projects-v2.png → docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-test-projects-v2.png

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

0
docs/zh-Hans/Tutorials/images/bookstore-user-management.png → docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-user-management.png

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

0
docs/zh-Hans/Tutorials/images/bookstore-visual-studio-solution-v3.png → docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-visual-studio-solution-v3.png

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

1078
docs/zh-Hans/Tutorials/Part-1.md

File diff suppressed because it is too large

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

File diff suppressed because it is too large

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

@ -1,198 +0,0 @@
## ASP.NET Core {{UI_Value}} 教程 - 第三章
````json
//[doc-params]
{
"UI": ["MVC","NG"]
}
````
{{
if UI == "MVC"
DB="ef"
DB_Text="Entity Framework Core"
UI_Text="mvc"
else if UI == "NG"
DB="mongodb"
DB_Text="MongoDB"
UI_Text="angular"
else
DB ="?"
UI_Text="?"
end
}}
### 关于本教程
这是ASP.NET Core{{UI_Value}}系列教程的第二章. 共有三章:
- [Part-1: 创建项目和书籍列表页面](Part-1.md)
- [Part 2: 创建,编辑,删除书籍](Part-2.md)
- **Part-3: 集成测试(本章)**
> 你也可以观看由ABP社区成员为本教程录制的[视频课程](https://amazingsolutions.teachable.com/p/lets-build-the-bookstore-application).
### 解决方案中的测试项目
解决方案中有多个测试项目:
![bookstore-test-projects-v2](./images/bookstore-test-projects-{{UI_Text}}.png)
每个项目用于测试相关的应用程序项目.测试项目使用以下库进行测试:
* [xunit](https://xunit.github.io/) 作为主测试框架.
* [Shoudly](http://shouldly.readthedocs.io/en/latest/) 作为断言库.
* [NSubstitute](http://nsubstitute.github.io/) 作为模拟库.
### 添加测试数据
启动模板包含`Acme.BookStore.TestBase`项目中的`BookStoreTestDataSeedContributor`类,它创建一些数据来运行测试.
更改`BookStoreTestDataSeedContributor`类如下所示:
````csharp
using System;
using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Guids;
namespace Acme.BookStore
{
public class BookStoreTestDataSeedContributor
: IDataSeedContributor, ITransientDependency
{
private readonly IRepository<Book, Guid> _bookRepository;
private readonly IGuidGenerator _guidGenerator;
public BookStoreTestDataSeedContributor(
IRepository<Book, Guid> bookRepository,
IGuidGenerator guidGenerator)
{
_bookRepository = bookRepository;
_guidGenerator = guidGenerator;
}
public async Task SeedAsync(DataSeedContext context)
{
await _bookRepository.InsertAsync(
new Book(id: _guidGenerator.Create(),
name: "Test book 1",
type: BookType.Fantastic,
publishDate: new DateTime(2015, 05, 24),
price: 21
)
);
await _bookRepository.InsertAsync(
new Book(id: _guidGenerator.Create(),
name: "Test book 2",
type: BookType.Science,
publishDate: new DateTime(2014, 02, 11),
price: 15
)
);
}
}
}
````
* 注入`IRepository<Book,Guid>`并在`SeedAsync`中使用它来创建两个书实体作为测试数据.
* 使用`IGuidGenerator`服务创建GUID. 虽然`Guid.NewGuid()`非常适合测试,但`IGuidGenerator`在使用真实数据库时还有其他特别重要的功能(参见[Guid生成文档](../Guid-Generation.md)了解更多信息).
### 测试 BookAppService
`Acme.BookStore.Application.Tests` 项目中创建一个名叫 `BookAppService_Tests` 的测试类:
````csharp
using System;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
using Shouldly;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Validation;
using Microsoft.EntityFrameworkCore.Internal;
namespace Acme.BookStore
{
public class BookAppService_Tests : BookStoreApplicationTestBase
{
private readonly IBookAppService _bookAppService;
public BookAppService_Tests()
{
_bookAppService = GetRequiredService<IBookAppService>();
}
[Fact]
public async Task Should_Get_List_Of_Books()
{
//Act
var result = await _bookAppService.GetListAsync(
new PagedAndSortedResultRequestDto()
);
//Assert
result.TotalCount.ShouldBeGreaterThan(0);
result.Items.ShouldContain(b => b.Name == "Test book 1");
}
}
}
````
* 测试方法 `Should_Get_List_Of_Books` 直接使用 `BookAppService.GetListAsync` 方法来获取用户列表,并执行检查.
新增测试方法,用以测试创建一个合法book实体的场景:
````C#
[Fact]
public async Task Should_Create_A_Valid_Book()
{
//Act
var result = await _bookAppService.CreateAsync(
new CreateUpdateBookDto
{
Name = "New test book 42",
Price = 10,
PublishDate = DateTime.Now,
Type = BookType.ScienceFiction
}
);
//Assert
result.Id.ShouldNotBe(Guid.Empty);
result.Name.ShouldBe("New test book 42");
}
````
新增测试方法,用以测试创建一个非法book实体失败的场景:
````csharp
[Fact]
public async Task Should_Not_Create_A_Book_Without_Name()
{
var exception = await Assert.ThrowsAsync<Volo.Abp.Validation.AbpValidationException>(async () =>
{
await _bookAppService.CreateAsync(
new CreateUpdateBookDto
{
Name = "",
Price = 10,
PublishDate = DateTime.Now,
Type = BookType.ScienceFiction
}
);
});
exception.ValidationErrors
.ShouldContain(err => err.MemberNames.Any(mem => mem == "Name"));
}
````
* 由于 `Name` 是空值, ABP 抛出一个 `AbpValidationException` 异常.
打开**测试资源管理器**(测试 -> Windows -> 测试资源管理器)并**执行**所有测试:
![bookstore-appservice-tests](./images/bookstore-appservice-tests.png)
恭喜, 绿色图标表示测试已成功通过!

BIN
docs/zh-Hans/Tutorials/images/bookstore-actions-buttons.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-angular-file-tree.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-confirmation-popup.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-create-project-angular.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-create-project-mvc.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 177 KiB

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

Loading…
Cancel
Save