|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 241 KiB |
|
Before Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 128 KiB |
@ -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: |
|||
|
|||
 |
|||
|
|||
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) |
|||
@ -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: |
|||
|
|||
 |
|||
|
|||
## 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: |
|||
|
|||
 |
|||
|
|||
## 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: |
|||
|
|||
 |
|||
|
|||
|
|||
## 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: |
|||
|
|||
 |
|||
|
|||
## How to Remove an Element From Right Part of the Menu |
|||
|
|||
TODO |
|||
|
|||
|
|||
## What's Next |
|||
|
|||
* [Component Replacement](./Component-Replacement.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: |
|||
|
|||
 |
|||
|
|||
### 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) |
|||
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 20 KiB |
@ -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... |
|||
@ -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**中打开解决方案: |
|||
|
|||
 |
|||
|
|||
该解决方案具有分层结构(基于[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`项目并选择 **设置为启动项目**: |
|||
|
|||
 |
|||
|
|||
按F5(或Ctrl + F5)运行应用程序. 它将具有如下所示的输出: |
|||
|
|||
 |
|||
|
|||
#### 使用EF Core Update-Database命令 |
|||
|
|||
Ef Core具有`Update-Database`命令, 可根据需要创建数据库并应用挂起的迁移. 右键单击`.Web`项目并选择**设置为启动项目**: |
|||
|
|||
 |
|||
|
|||
打开**包管理器控制台(Package Manager Console)**, 选择`.EntityFrameworkCore.DbMigrations`项目作为**默认项目**并运行`Update-Database`命令: |
|||
|
|||
 |
|||
|
|||
这将基于配置的连接字符串创建新数据库. |
|||
|
|||
> 使用`.Migrator`工具是建议的方法, 因为它还能初始化初始数据能够正确运行Web应用程序. |
|||
|
|||
### 运行应用程序 |
|||
|
|||
你现在可以运行应用程序,它将会打开**home**页面: |
|||
|
|||
 |
|||
|
|||
单击 **登录** 按钮, 输入用户名`admin`, 密码`1q2w3E*`, 登录应用程序. |
|||
|
|||
启动模板包括**身份管理**和**租户管理**模块. 登录后,将显示"管理"菜单, 你可以在其中管理**租户**,**角色**,**用户**和**权限**. 用户管理页面如下所示: |
|||
|
|||
 |
|||
|
|||
### 下一步是什么? |
|||
|
|||
* [应用程序开发教程](Tutorials/AspNetCore-Mvc/Part-I.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) |
|||
@ -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" }} |
|||
|
|||
创建项目后你会有以下解决方案目录和文件: |
|||
|
|||
 |
|||
|
|||
在Visual Studio中打开 `.sln` 文件时,将看到以下解决方案结构: |
|||
|
|||
{{if DB == "Mongo"}} |
|||
|
|||
 |
|||
|
|||
{{else}} |
|||
|
|||
 |
|||
|
|||
{{end}} |
|||
|
|||
{{ else if UI == "NG" }} |
|||
在创建的解决方案中有三个文件夹: |
|||
|
|||
 |
|||
|
|||
* `angular` 文件夹包含Angular UI应用程序. |
|||
* `aspnet-core` 文件夹包含后端应用程序. |
|||
* `react-native` 文件夹包含React Native UI 应用程序. |
|||
|
|||
打开 `aspnet-core` 文件夹下的 `.sln`(`Visual Studio`解决方案)文件: |
|||
 |
|||
|
|||
{{ 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`项目并选择 **设置为启动项目**: |
|||
|
|||
 |
|||
|
|||
按F5(或Ctrl + F5)运行应用程序. 它将具有如下所示的输出: |
|||
|
|||
 |
|||
|
|||
#### 使用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 }} |
|||
|
|||
 |
|||
|
|||
打开**包管理器控制台(Package Manager Console)**, 选择`.EntityFrameworkCore.DbMigrations`项目作为**默认项目**并运行`Update-Database`命令: |
|||
|
|||
 |
|||
|
|||
这将基于配置的连接字符串创建新数据库. |
|||
|
|||
> 使用`.Migrator`工具是建议的方法, 因为它还能初始化初始数据能够正确运行Web应用程序. |
|||
|
|||
{{ else if DB == "Mongo" }} |
|||
|
|||
````json |
|||
"ConnectionStrings": { |
|||
"Default": "mongodb://localhost:27017/BookStore" |
|||
} |
|||
```` |
|||
|
|||
该解决方案被配置为在你的本地计算机中使用 **MongoDB**,因此你需要启动并运行一个MongoDB服务器实例或者将连接字符串更改为另一个MongoDB服务器. |
|||
|
|||
### 初始化种子数据 |
|||
|
|||
该解决方案附带一个 `.DbMigrator` 控制台应用程序,该应用程序为初始数据提供了种子. 它对于开发以及生产环境都很有用. |
|||
|
|||
> `.DbMigrator` 项目有自己的 `appsettings.json`.如果你更改了其他项目的 `appsettings.json`,也应该更改这个. |
|||
|
|||
右键点击 `.DbMigrator` 并选择 **设置为启动项目**. |
|||
|
|||
 |
|||
|
|||
按F5(或Ctrl+F5)启动应用程序,你会看到以下输出: |
|||
|
|||
 |
|||
|
|||
> 数据库创建后会初始化种子数据, 其中包含用于登录的 `admin` 用户. 所以你至少使用 `.DbMigrator` 一次. |
|||
|
|||
{{ end }} |
|||
|
|||
### 运行应用程序 |
|||
|
|||
{{ if UI == "MVC" }} |
|||
|
|||
{{ if Tiered == "Yes" }} |
|||
|
|||
确保 `.IdentityServer` 是启动项目,运行应用程序后会在你的浏览器打开一个 **login** 页面. |
|||
|
|||
> 在Visual Studio中使用Ctrl+F5(而不是F5)运行应用,如果你不用于调试,这会减少启动时间. |
|||
|
|||
你可以登录,但是不能在这里进入主应用程序,它仅是验证服务器. |
|||
|
|||
确保 `.HttpApi.Host` 是启动项目,运行应用程序后会在你的浏览器打开一个 **Swagger UI** 页面. |
|||
|
|||
 |
|||
|
|||
这里是Web应用程序使用的API应用程序. |
|||
|
|||
最后确保 `.Web` 是启动项目,运行应用程序后会在你的浏览器打开一个 **welcome** 页面. |
|||
|
|||
 |
|||
|
|||
点击 **login** 按钮重定向到 `Identity Server` 来登录应用程序. |
|||
|
|||
 |
|||
|
|||
{{ else }} |
|||
|
|||
最后确保 `.Web` 是启动项目,运行应用程序后会在你的浏览器打开一个 **login** 页面. |
|||
|
|||
> 在Visual Studio中使用Ctrl+F5(而不是F5)运行应用,如果你不用于调试,这会减少启动时间. |
|||
|
|||
 |
|||
|
|||
{{ 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 }} |
|||
|
|||
 |
|||
|
|||
你可以看到应用程序的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/). |
|||
|
|||
 |
|||
|
|||
{{ end }} |
|||
|
|||
输入用户名 **admin**,密码 **1q2w3E*** 登录到应用程序. |
|||
|
|||
 |
|||
|
|||
应用程序已经启动并执行,你可以基于该启动模板开发应用程序. |
|||
|
|||
#### 移动开发 |
|||
|
|||
ABP平台提供了[React Native](https://reactnative.dev/)模板用于开发移动应用程序. |
|||
|
|||
> 该解决方案默认 `react-native` 包含了React Native应用程序,如果你不计划使用React Native开发移动应用程序,你可以忽略并删除 `react-native` 文件夹. |
|||
|
|||
运行在Android模拟器或真机上的React Native应用程序无法连接到 `localhost` 上的后.要修复此问题,需要在本地IP上运行后端. |
|||
|
|||
{{ if Tiered == "No"}} |
|||
 |
|||
|
|||
* 打开 `.HttpApi.Host` 文件夹下的 `appsettings.json` 文件. 将 `SelfUrl` 和 `Authority` 属性的 `localhost` 替换为你本地的IP地址. |
|||
* 打开 `.HttpApi.Host/Properties` 文件夹下的 `launchSettings.json` 文件. 将 `applicationUrl` 属性的 `localhost` 替换为你本地的IP地址. |
|||
|
|||
{{ else if Tiered == "Yes" }} |
|||
|
|||
 |
|||
|
|||
* 打开 `.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地址: |
|||
|
|||
 |
|||
|
|||
{{ 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 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/). |
|||
|
|||
 |
|||
|
|||
输入用户名 **admin**,密码 **1q2w3E*** 登录到应用程序. |
|||
|
|||
应用程序已经启动并执行,你可以基于该启动模板开发应用程序. |
|||
|
|||
> [应用程序启动模板](Startup-Templates/Application.md) 包含租户管理和Identity模块. |
|||
|
|||
## 下一步是什么? |
|||
|
|||
[应用程序开发教程](Tutorials/Part-1.md) |
|||
@ -1,3 +0,0 @@ |
|||
## 规约 |
|||
|
|||
TODO.. |
|||
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
@ -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). |
|||
|
|||
### 解决方案中的测试项目 |
|||
|
|||
解决方案中有多个测试项目: |
|||
|
|||
 |
|||
|
|||
每个项目用于测试相关的应用程序项目.测试项目使用以下库进行测试: |
|||
|
|||
* [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 -> 测试资源管理器)并**执行**所有测试: |
|||
|
|||
 |
|||
|
|||
恭喜, 绿色图标表示测试已成功通过! |
|||
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 177 KiB |