@ -0,0 +1,12 @@ |
|||
name: cancel-workflow |
|||
on: [push] |
|||
jobs: |
|||
cancel: |
|||
name: 'Cancel Previous Runs' |
|||
runs-on: ubuntu-latest |
|||
timeout-minutes: 3 |
|||
steps: |
|||
- uses: styfle/cancel-workflow-action@0.6.0 |
|||
with: |
|||
workflow_id: 10629,1299107,2792859,8268314 |
|||
access_token: ${{ github.token }} |
|||
@ -0,0 +1,216 @@ |
|||
# LeptonX Alpha Release |
|||
|
|||
We are excited to announce that the **alpha version** of the LeptonX theme has been released! As stated in [this blog post](https://volosoft.com/blog/introducing-the-lepton-theme-next-generation), LeptonX comes in different shapes. For this release, we introduce only ABP based projects with the Angular UI. So, if you are already using the ABP Framework and Angular as the frontend choice, you can integrate these packages into your project today. |
|||
|
|||
The theme has been deployed with two versions: LeptonX-lite (free) and LeptonX (commercial). |
|||
|
|||
> **Note that this theme currently only works for the *Angular UI*. Please keep waiting for other UI options.** |
|||
|
|||
## Open-Source |
|||
|
|||
This section shows how to replace the basic theme (that comes with open source ABP Framework startup template) with the new LeptonX-lite theme. |
|||
|
|||
To add `LeptonX-lite` into your project, |
|||
|
|||
* Install `@abp/ng.theme.lepton-x` NPM package |
|||
|
|||
`npm install @abp/ng.theme.lepton-x@preview` or |
|||
|
|||
`yarn add @abp/ng.theme.lepton-x@preview` |
|||
|
|||
* Then, we need to edit the styles array in `angular.json` to replace the existing style with the new one. |
|||
|
|||
Replace the following style |
|||
|
|||
```JSON |
|||
{ |
|||
"input": "node_modules/bootstrap/dist/css/bootstrap.min.css", |
|||
"inject": true, |
|||
"bundleName": "bootstrap-ltr.min" |
|||
}, |
|||
``` |
|||
|
|||
with |
|||
|
|||
```json |
|||
"node_modules/@volo/ngx-lepton-x.lite/styles/sidemenu-layout.min.css", |
|||
"node_modules/bootstrap-icons/font/bootstrap-icons.css", |
|||
``` |
|||
|
|||
* Finally, remove `ThemeBasicModule` from `app.module.ts` and `shared.module.ts`, and import the related modules in `app.module.ts` |
|||
|
|||
```js |
|||
import { ThemeLeptonXModule } from '@abp/ng.theme.lepton-x'; |
|||
import { SideMenuLayoutModule } from '@abp/ng.theme.lepton-x/layouts'; |
|||
|
|||
@NgModule({ |
|||
imports: [ |
|||
// ... |
|||
ThemeLeptonXModule.forRoot(), |
|||
SideMenuLayoutModule.forRoot(), |
|||
], |
|||
// ... |
|||
}) |
|||
export class AppModule {} |
|||
``` |
|||
|
|||
Note: If you employ [Resource Owner Password Flow](https://docs.abp.io/en/abp/latest/UI/Angular/Authorization#resource-owner-password-flow) for authorization, you should import the following module as well: |
|||
|
|||
```js |
|||
import { AccountLayoutModule } from '@abp/ng.theme.lepton-x/account'; |
|||
|
|||
@NgModule({ |
|||
// ... |
|||
imports: [ |
|||
// ... |
|||
AccountLayoutModule.forRoot(), |
|||
// ... |
|||
], |
|||
// ... |
|||
}) |
|||
export class AppModule {} |
|||
``` |
|||
|
|||
To change the logos and brand color of the `LeptonX`, simply add the following CSS to the `styles.scss` |
|||
|
|||
```css |
|||
:root { |
|||
--lpx-logo: url('/assets/images/logo.png'); |
|||
--lpx-logo-icon: url('/assets/images/logo-icon.png'); |
|||
--lpx-brand: #edae53; |
|||
} |
|||
``` |
|||
|
|||
- `--lpx-logo` is used to place the logo in the menu. |
|||
- `--lpx-logo-icon` is a square icon used when the menu is collapsed. |
|||
- `--lpx-brand` is a color used throughout the application, especially on active elements. |
|||
|
|||
 |
|||
|
|||
 |
|||
|
|||
## ABP Commercial |
|||
|
|||
This section shows how to replace the lepton theme (that comes with the ABP Commercial startup template) with the new LeptonX theme. |
|||
|
|||
To add `LeptonX` into your existing projects, |
|||
|
|||
* Firstly, install `@volosoft/abp.ng.theme.lepton-x` NPM package |
|||
|
|||
`npm install @volosoft/abp.ng.theme.lepton-x@preview` or |
|||
|
|||
`yarn add @volosoft/abp.ng.theme.lepton-x@preview` |
|||
|
|||
* Then, edit `angular.json` as follows: |
|||
|
|||
Remove the following config from the `styles` array since LeptonX provides bootstrap as embedded in its CSS. |
|||
|
|||
```JSON |
|||
{ |
|||
"input": "node_modules/bootstrap/dist/css/bootstrap.min.css", |
|||
"inject": true, |
|||
"bundleName": "bootstrap-ltr.min" |
|||
}, |
|||
``` |
|||
|
|||
Add the following ones into the `styles` array |
|||
|
|||
```JSON |
|||
{ |
|||
"input": "node_modules/@volosoft/ngx-lepton-x/styles/themes/dark.css", |
|||
"inject": false, |
|||
"bundleName": "lepton-x.dark" |
|||
}, |
|||
{ |
|||
"input": "node_modules/@volosoft/ngx-lepton-x/styles/themes/dim.css", |
|||
"inject": false, |
|||
"bundleName": "lepton-x.dim" |
|||
}, |
|||
{ |
|||
"input": "node_modules/@volosoft/ngx-lepton-x/styles/themes/light.css", |
|||
"inject": false, |
|||
"bundleName": "lepton-x.light" |
|||
}, |
|||
"node_modules/@volosoft/ngx-lepton-x/styles/css/sidemenu-layout.min.css", |
|||
"node_modules/bootstrap-icons/font/bootstrap-icons.css", |
|||
``` |
|||
|
|||
Three of them are related to the theming and will be loaded during runtime. That's why they are not injected into the `head` as a style. Hence, the `"inject": false` |
|||
|
|||
The fourth one depends on which layout you want to use. For now, there is only `sidemenu-layout` available. In the future, there will be many layouts to choose from. |
|||
|
|||
The last one is `bootstrap-icons` which are being used throughout the components. |
|||
|
|||
* At last, remove `ThemeLeptonModule` from `app.module.ts` and `shared.module.ts`, and import the following modules in `app.module.ts` |
|||
|
|||
```js |
|||
import { ThemeLeptonXModule } from '@volosoft/abp.ng.theme.lepton-x'; |
|||
import { AbpSideMenuLayoutModule } from '@volosoft/abp.ng.theme.lepton-x/layouts'; |
|||
|
|||
@NgModule({ |
|||
// ... |
|||
imports: [ |
|||
// ... |
|||
ThemeLeptonXModule.forRoot(), |
|||
AbpSideMenuLayoutModule.forRoot(), // depends on which layout you choose |
|||
// ... |
|||
], |
|||
// ... |
|||
}) |
|||
export class AppModule {} |
|||
``` |
|||
|
|||
Note: If you employ [Resource Owner Password Flow](https://docs.abp.io/en/abp/latest/UI/Angular/Authorization#resource-owner-password-flow) for authorization, you should import the following module as well: |
|||
|
|||
```js |
|||
import { AccountLayoutModule } from '@volosoft/abp.ng.theme.lepton-x/account'; |
|||
|
|||
@NgModule({ |
|||
// ... |
|||
imports: [ |
|||
// ... |
|||
AccountLayoutModule.forRoot({ |
|||
layout: { |
|||
authLayoutImg: '/assets/images/login-bg.jpg', |
|||
}, |
|||
}), |
|||
// ... |
|||
], |
|||
// ... |
|||
}) |
|||
export class AppModule {} |
|||
``` |
|||
|
|||
`authLayoutImg`: (Optional) If not given, a default image will be placed on the authentication pages. |
|||
|
|||
|
|||
* At this point, `LeptonX` theme should be up and running within your application. However, you may need to overwrite some css variables based your needs for every theme available as follows: |
|||
```scss |
|||
:root { |
|||
.lpx-theme-dark { |
|||
--lpx-logo: url('/assets/images/logo/logo-light.svg'); |
|||
--lpx-logo-icon: url('/assets/images/logo/logo-light-icon.svg'); |
|||
--lpx-brand: #edae53; |
|||
} |
|||
|
|||
.lpx-theme-dim { |
|||
--lpx-logo: url('/assets/images/logo/logo-light.svg'); |
|||
--lpx-logo-icon: url('/assets/images/logo/logo-light-icon.svg'); |
|||
--lpx-brand: #f15835; |
|||
} |
|||
|
|||
.lpx-theme-light { |
|||
--lpx-logo: url('/assets/images/logo/logo-dark.svg'); |
|||
--lpx-logo-icon: url('/assets/images/logo/logo-dark-icon.svg'); |
|||
--lpx-brand: #69aada; |
|||
} |
|||
} |
|||
``` |
|||
|
|||
When the user selects a theme, the corresponding CSS class is added to the `body`, so you can write specific CSS rules to each theme. |
|||
|
|||
## Conclusion |
|||
|
|||
In this blog post, I've explained how to use the alpha version of the new LeptonX theme for ABP-based solutions. Please, keep in mind that this is an alpha version, and we will continue to work on the LeptonX theme. The APIs are bound to change and breaking changes may be introduced in future versions. |
|||
|
|||
We would like you to try it out with the latest version of the ABP Framework and give us feedback at lepton@volosoft.com or open an issue on this repository: https://github.com/volosoft/lepton-theme |
|||
|
After Width: | Height: | Size: 155 KiB |
|
After Width: | Height: | Size: 152 KiB |
@ -0,0 +1,3 @@ |
|||
## Concurrency Check |
|||
|
|||
TODO |
|||
@ -0,0 +1,830 @@ |
|||
# 快速入门 |
|||
|
|||
````json |
|||
//[doc-params] |
|||
{ |
|||
"UI": ["MVC", "Blazor", "BlazorServer", "NG"], |
|||
"DB": ["EF", "Mongo"] |
|||
} |
|||
```` |
|||
|
|||
这是一个由单个部分组成的快速入门教程, 旨在使用ABP框架构建一个简单的待办事项应用程序. 这是最终应用程序的屏幕截图: |
|||
|
|||
 |
|||
|
|||
你可以在[这里](https://github.com/abpframework/abp-samples/tree/master/TodoApp)找到已完成的项目源代码. |
|||
|
|||
## 先决条件 |
|||
|
|||
* 一个集成开发环境 (比如: [Visual Studio](https://visualstudio.microsoft.com/vs/)) 它需要支持 [.NET 5.0+](https://dotnet.microsoft.com/download/dotnet) 的开发. |
|||
|
|||
{{if DB=="Mongo"}} |
|||
|
|||
* [MongoDB Server 4.0+](https://docs.mongodb.com/manual/administration/install-community/) |
|||
|
|||
{{end}} |
|||
|
|||
{{if UI=="NG"}} |
|||
|
|||
* [Node v14.x](https://nodejs.org/) |
|||
|
|||
{{end}} |
|||
|
|||
## 创建新的解决方案 |
|||
|
|||
我们将使用[ABP CLI](../../CLI.md) 创建带有ABP框架的新解决方案. 你可以在命令行终端中运行以下命令来安装它: |
|||
|
|||
````bash |
|||
dotnet tool install -g Volo.Abp.Cli |
|||
```` |
|||
|
|||
然后创建一个空文件夹, 打开命令行终端并在终端中执行以下命令: |
|||
|
|||
````bash |
|||
abp new TodoApp{{if UI=="Blazor"}} -u blazor{{else if UI=="BlazorServer"}} -u blazor-server{{else if UI=="NG"}} -u angular{{end}}{{if DB=="Mongo"}} -d mongodb{{end}} |
|||
```` |
|||
|
|||
{{if UI=="NG"}} |
|||
|
|||
这将创建一个名为*TodoApp*的新解决方案, 其中包含`angular`和`aspnet core`文件夹. 一旦解决方案就绪, 请在你最喜欢的 IDE 中打开 ASP.NET Core 解决方案. |
|||
|
|||
{{else}} |
|||
|
|||
这将创建一个名为*TodoApp*的新解决方案. 一旦解决方案就绪, 请在你最喜欢的 IDE 中打开它. |
|||
|
|||
{{end}} |
|||
|
|||
### 创建数据库 |
|||
|
|||
如果你使用的是Visual Studio, 请右键单击`TodoApp.DbMigrator`项目, 选择*设置为启动项目*, 然后按*Ctrl+F5*运行它而不进行调试. 它将创建初始数据库并生成初始数据. |
|||
|
|||
{{if DB=="EF"}} |
|||
|
|||
> 一些IDE (例如Rider) 可能会在第一次运行时遇到问题, 因为 *DbMigrator* 添加初始迁移并重新编译项目. 在这种情况下, 在 `.DbMigrator` 项目文件夹中打开命令行终端并执行 `dotnet run` 命令. |
|||
|
|||
{{end}} |
|||
|
|||
### 运行应用程序 |
|||
|
|||
{{if UI=="MVC" || UI=="BlazorServer"}} |
|||
|
|||
最好在开始开发之前运行一下应用程序. 确保 {{if UI=="BlazorServer"}}`TodoApp.Blazor`{{else}}`TodoApp.Web`{{end}} 是启动项目, 然后运行应用程序(Visual Studio中是Ctrl+F5)来查看初始UI: |
|||
|
|||
{{else if UI=="Blazor"}} |
|||
|
|||
最好在开始开发之前运行一下应用程序. 解决方案中有两个主要应用程序 |
|||
|
|||
* `TodoApp.HttpApi.Host` 承载服务器端的 HTTP API. |
|||
* `TodoApp.Blazor` 是客户端的Blazor WebAssembly应用程序. |
|||
|
|||
确保 `TodoApp.HttpApi.Host` 是启动项目, 然后运行应用程序(Visual Studio中的Ctrl+F5)打开[Swagger UI](https://swagger.io/tools/swagger-ui/)来查看服务器端的 HTTP API: |
|||
|
|||
 |
|||
|
|||
你可以使用此 UI 探索和测试你的 HTTP API. 现在, 我们可以将 `TodoApp.Blazor` 设置为启动项目并运行它来打开实际的 Blazor 应用程序UI: |
|||
|
|||
{{else if UI=="NG"}} |
|||
|
|||
最好在开始开发之前运行一下应用程序. 解决方案中有两个主要应用程序; |
|||
|
|||
* `TodoApp.HttpApi.Host` (在.NET解决方案中)承载服务器端HTTP API. |
|||
* `Angular` 文件夹包含 Angular 应用程序. |
|||
|
|||
确保 `TodoApp.HttpApi.Host` 是启动项目, 然后运行应用程序(Visual Studio中的Ctrl+F5)打开[Swagger UI](https://swagger.io/tools/swagger-ui/)来查看服务器端的 HTTP API: |
|||
|
|||
 |
|||
|
|||
你可以使用此 UI 探索和测试你的 HTTP API. 如果该功能正常, 我们可以运行Angular 客户端应用程序. |
|||
|
|||
首先, 运行以下命令来还原NPM包: |
|||
|
|||
````bash |
|||
npm install |
|||
```` |
|||
|
|||
安装所有软件包需要一些时间. 然后可以使用以下命令运行应用程序: |
|||
|
|||
````bash |
|||
npm start |
|||
```` |
|||
|
|||
此命令需要时间, 但最终在你的默认浏览器中运行并打开应用程序: |
|||
|
|||
{{end}} |
|||
|
|||
 |
|||
|
|||
你可以单击 *登录* 按钮, 以`admin`作为用户名和`1q2w3E*` 作为密码登录到应用程序. |
|||
|
|||
一切就绪. 我们可以开始编程! |
|||
|
|||
## 领域层 |
|||
|
|||
此应用程序只有一个 [实体](../../Entities.md), 接下来我们开始创建它. 在 *TodoApp.Domain* 项目中创建一个新的 `TodoItem` 类: |
|||
|
|||
````csharp |
|||
using System; |
|||
using Volo.Abp.Domain.Entities; |
|||
|
|||
namespace TodoApp |
|||
{ |
|||
public class TodoItem : BasicAggregateRoot<Guid> |
|||
{ |
|||
public string Text { get; set; } |
|||
} |
|||
} |
|||
```` |
|||
|
|||
`BasicAggregateRoot` 是创建根实体的最简单的基础类. `Guid` 是这里实体的主键 (`Id`). |
|||
|
|||
## 数据库集成 |
|||
|
|||
{{if DB=="EF"}} |
|||
|
|||
下一步是设置 [Entity Framework Core](../../Entity-Framework-Core.md)配置 |
|||
|
|||
### 映射配置 |
|||
|
|||
打开在 *TodoApp.EntityFrameworkCore* 项目中 `EntityFrameworkCore` 文件夹中的 `TodoAppDbContext` 类, 并向该类添加新的 `DbSet` 属性: |
|||
|
|||
````csharp |
|||
public DbSet<TodoItem> TodoItems { get; set; } |
|||
```` |
|||
|
|||
然后在 `TodoAppDbContext` 类中定位到 `OnModelCreating` 方法, 并为 `TodoItem` 实体添加映射代码: |
|||
|
|||
````csharp |
|||
protected override void OnModelCreating(ModelBuilder builder) |
|||
{ |
|||
base.OnModelCreating(builder); |
|||
|
|||
/* Include modules to your migration db context */ |
|||
|
|||
builder.ConfigurePermissionManagement(); |
|||
... |
|||
|
|||
/* Configure your own tables/entities inside here */ |
|||
builder.Entity<TodoItem>(b => |
|||
{ |
|||
b.ToTable("TodoItems"); |
|||
}); |
|||
} |
|||
```` |
|||
|
|||
我们已经将 `TodoItem` 实体映射到数据库中的 `TodoItems` 表. |
|||
|
|||
### Code First 迁移 |
|||
|
|||
解决方案快速模版已经配置为使用Entity Framework Core的 [Code First 迁移](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations). 由于我们已经更改了数据库映射配置, 因此我们应该创建一个新的迁移并将更改应用于数据库. |
|||
|
|||
在 *TodoApp.EntityFrameworkCore* 项目目录中打开一个命令行终端并输入以下命令: |
|||
|
|||
````bash |
|||
dotnet ef migrations add Added_TodoItem |
|||
```` |
|||
|
|||
这将向项目添加一个新的迁移类: |
|||
|
|||
 |
|||
|
|||
你可以在同一命令行终端中使用以下命令将更改应用于数据库: |
|||
|
|||
````bash |
|||
dotnet ef database update |
|||
```` |
|||
|
|||
> 如果你使用的是Visual Studio, 则可能希望在 *包管理器控制台 (PMC)* 中使用 `Add-Migration Added_TodoItem` 和 `Update-Database` 命令. 在这种情况下, 请确保 {{if UI=="MVC"}}`TodoApp.Web`{{else if UI=="BlazorServer"}}`TodoApp.Blazor`{{else if UI=="Blazor" || UI=="NG"}}`TodoApp.HttpApi.Host`{{end}} 是启动项目, 并且 `TodoApp.EntityFrameworkCore` 是PMC中的 *默认项目*. |
|||
|
|||
{{else if DB=="Mongo"}} |
|||
|
|||
下一步是设置 [MongoDB](../../MongoDB.md) 配置. 打开 *TodoApp.MongoDB* 项目中 `MongoDb` 文件夹的`TodoAppMongoDbContext` 类并做出以下更改; |
|||
|
|||
1. 向类添加新属性: |
|||
|
|||
````csharp |
|||
public IMongoCollection<TodoItem> TodoItems => Collection<TodoItem>(); |
|||
```` |
|||
|
|||
2. 在 `CreateModel` 方法中添加以下代码: |
|||
|
|||
````csharp |
|||
modelBuilder.Entity<TodoItem>(b => |
|||
{ |
|||
b.CollectionName = "TodoItems"; |
|||
}); |
|||
```` |
|||
|
|||
{{end}} |
|||
|
|||
现在, 我们可以使用ABP仓库保存和检索待办事项, 如我们在下一节所做的那样. |
|||
|
|||
## 应用层 |
|||
|
|||
[应用程序服务](../../Application-Services.md) 用于执行应用程序的用例. 我们需要执行以下用例: |
|||
|
|||
* 获取待办事项列表 |
|||
* 创建新的待办事项 |
|||
* 删除现有的待办事项 |
|||
|
|||
### 应用服务接口 |
|||
|
|||
我们可以先从为应用程序服务定义接口开始. 在 *TodoApp.Application.Contracts* 项目中创建新的 `ITodoAppService` 接口, 如下所示: |
|||
|
|||
````csharp |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace TodoApp |
|||
{ |
|||
public interface ITodoAppService : IApplicationService |
|||
{ |
|||
Task<List<TodoItemDto>> GetListAsync(); |
|||
Task<TodoItemDto> CreateAsync(string text); |
|||
Task DeleteAsync(Guid id); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
### 数据传输对象 |
|||
|
|||
`GetListAsync` 和`CreateAsync` 方法返回 `TodoItemDto`. `应用程序服务` 通常获取并返回 DTO([数据传输对象](../../Data-Transfer-Objects.md)) 而不是实体. 因此, 我们应该在这里定义DTO类. 在 *TodoApp.Application.Contracts* 项目中创建新的 `TodoItemDto` 类: |
|||
|
|||
````csharp |
|||
using System; |
|||
|
|||
namespace TodoApp |
|||
{ |
|||
public class TodoItemDto |
|||
{ |
|||
public Guid Id { get; set; } |
|||
public string Text { get; set; } |
|||
} |
|||
} |
|||
```` |
|||
|
|||
这是一个非常简单的DTO类, 它与我们的 `TodoItem` 实体相对应. 接下来, 我们准备实现 `ITodoAppService`接口. |
|||
|
|||
### 应用服务实现 |
|||
|
|||
在 *TodoApp.Application* 项目中创建 `TodoAppService` 类, 如下所示: |
|||
|
|||
````csharp |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Services; |
|||
using Volo.Abp.Domain.Repositories; |
|||
|
|||
namespace TodoApp |
|||
{ |
|||
public class TodoAppService : ApplicationService, ITodoAppService |
|||
{ |
|||
private readonly IRepository<TodoItem, Guid> _todoItemRepository; |
|||
|
|||
public TodoAppService(IRepository<TodoItem, Guid> todoItemRepository) |
|||
{ |
|||
_todoItemRepository = todoItemRepository; |
|||
} |
|||
|
|||
// TODO: Implement the methods here... |
|||
} |
|||
} |
|||
```` |
|||
|
|||
该类继承自ABP框架的`ApplicationService`类, 并实现了之前定义的 `ITodoAppService`接口. ABP为实体提供默认的泛型 [仓储](../../Repositories.md). 我们可以使用它们来执行基本的数据库操作. 此类中 [注入](../../Dependency-Injection.md) 的 `IRepository<TodoItem, Guid>`, 它就是 `TodoItem` 实体的默认存储库. 我们将使用它来实现之前描述的用例. |
|||
|
|||
#### 获取待办事项 |
|||
|
|||
让我们先实现 `GetListAsync` 方法: |
|||
|
|||
````csharp |
|||
public async Task<List<TodoItemDto>> GetListAsync() |
|||
{ |
|||
var items = await _todoItemRepository.GetListAsync(); |
|||
return items |
|||
.Select(item => new TodoItemDto |
|||
{ |
|||
Id = item.Id, |
|||
Text = item.Text |
|||
}).ToList(); |
|||
} |
|||
```` |
|||
|
|||
我们只是从数据库中获取完整的`TodoItem`列表, 将它们映射到`TodoItemDto`对象并作为结果返回. |
|||
|
|||
#### 创建一个新的待办事项 |
|||
|
|||
下一个我们可以实现方法是 `CreateAsync` , 如下所示: |
|||
|
|||
````csharp |
|||
public async Task<TodoItemDto> CreateAsync(string text) |
|||
{ |
|||
var todoItem = await _todoItemRepository.InsertAsync( |
|||
new TodoItem {Text = text} |
|||
); |
|||
|
|||
return new TodoItemDto |
|||
{ |
|||
Id = todoItem.Id, |
|||
Text = todoItem.Text |
|||
}; |
|||
} |
|||
```` |
|||
|
|||
仓储的 `InsertAsync` 方法将给定的`TodoItem`插入数据库, 并返回相同的`TodoItem` 对象. 它还设置了`Id`, 因此我们可以在返回对象上使用它. 我们只是通过从新的 `TodoItem` 实体创建和返回 `TodoItemDto`. |
|||
|
|||
#### 删除待办事项 |
|||
|
|||
最后, 我们来实现 `DeleteAsync` 方法, 代码如下: |
|||
|
|||
````csharp |
|||
public async Task DeleteAsync(Guid id) |
|||
{ |
|||
await _todoItemRepository.DeleteAsync(id); |
|||
} |
|||
```` |
|||
|
|||
至此, 应用程序服务已准备好了让UI层来使用. |
|||
|
|||
## 用户界面层 |
|||
|
|||
现在是在UI上显示待办事项的时候了! 在开始编写代码之前, 最好记住我们正在尝试构建的内容. 这里是示例程序的最终用户界面的截图: |
|||
|
|||
 |
|||
|
|||
> **我们将在本教程中保持最简洁的UI端, 以使本教程简单且重点突出. 请参阅 [web应用程序开发教程](../Part-1.md) 来了解构建实际应用各个方面.** |
|||
|
|||
{{if UI=="MVC"}} |
|||
|
|||
### Index.cshtml.cs |
|||
|
|||
打开在 *TodoApp.Web* 项目的 `Pages` 文件夹中`Index.cshtml.cs`文件, 并用以下代码块替换它的默认内容: |
|||
|
|||
````csharp |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace TodoApp.Web.Pages |
|||
{ |
|||
public class IndexModel : TodoAppPageModel |
|||
{ |
|||
public List<TodoItemDto> TodoItems { get; set; } |
|||
|
|||
private readonly ITodoAppService _todoAppService; |
|||
|
|||
public IndexModel(ITodoAppService todoAppService) |
|||
{ |
|||
_todoAppService = todoAppService; |
|||
} |
|||
|
|||
public async Task OnGetAsync() |
|||
{ |
|||
TodoItems = await _todoAppService.GetListAsync(); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
此类使用 `ITodoAppService` 获取待办事项列表并将它赋值给 `TodoItems` 属性. 我们将用它来渲染razor页面上的待办事项目列表. |
|||
|
|||
### Index.cshtml |
|||
|
|||
打开 *TodoApp.Web* 项目中`Pages`文件夹下的`Index.cshtml`, 并替换为以下内容: |
|||
|
|||
````xml |
|||
@page |
|||
@model TodoApp.Web.Pages.IndexModel |
|||
@section styles { |
|||
<abp-style src="/Pages/Index.css" /> |
|||
} |
|||
@section scripts { |
|||
<abp-script src="/Pages/Index.js" /> |
|||
} |
|||
<div class="container"> |
|||
<abp-card> |
|||
<abp-card-header> |
|||
<abp-card-title> |
|||
TODO LIST |
|||
</abp-card-title> |
|||
</abp-card-header> |
|||
<abp-card-body> |
|||
<!-- FORM FOR NEW TODO ITEMS --> |
|||
<form id="NewItemForm" class="form-inline"> |
|||
<input id="NewItemText" |
|||
type="text" |
|||
class="form-control mr-2" |
|||
placeholder="enter text..."> |
|||
<button type="submit" class="btn btn-primary">Submit</button> |
|||
</form> |
|||
|
|||
<!-- TODO ITEMS LIST --> |
|||
<ul id="TodoList"> |
|||
@foreach (var todoItem in Model.TodoItems) |
|||
{ |
|||
<li data-id="@todoItem.Id"> |
|||
<i class="fa fa-trash-o"></i> @todoItem.Text |
|||
</li> |
|||
} |
|||
</ul> |
|||
</abp-card-body> |
|||
</abp-card> |
|||
</div> |
|||
```` |
|||
|
|||
我们使用 ABP 的 [卡片标签助手](../../UI/AspNetCore/Tag-Helpers/Cards.md) 来创建一个简单的卡片视图. 你可以直接使用标准引导HTML结构, 但ABP [标签助手](../../UI/AspNetCore/Tag-Helpers/Index.md) 使它更容易并且更安全. |
|||
|
|||
此页面导入一个 CSS 和 JavaScript 文件, 所以我们需要创建它们. |
|||
|
|||
### Index.js |
|||
|
|||
打开 *TodoApp.Web* 项目中`Pages`文件夹下的`Index.js`, 并替换为以下内容: |
|||
|
|||
````js |
|||
$(function () { |
|||
|
|||
// DELETING ITEMS ///////////////////////////////////////// |
|||
$('#TodoList').on('click', 'li i', function(){ |
|||
var $li = $(this).parent(); |
|||
var id = $li.attr('data-id'); |
|||
|
|||
todoApp.todo.delete(id).then(function(){ |
|||
$li.remove(); |
|||
abp.notify.info('Deleted the todo item.'); |
|||
}); |
|||
}); |
|||
|
|||
// CREATING NEW ITEMS ///////////////////////////////////// |
|||
$('#NewItemForm').submit(function(e){ |
|||
e.preventDefault(); |
|||
|
|||
var todoText = $('#NewItemText').val(); |
|||
todoApp.todo.create(todoText).then(function(result){ |
|||
$('<li data-id="' + result.id + '">') |
|||
.html('<i class="fa fa-trash-o"></i> ' + result.text) |
|||
.appendTo($('#TodoList')); |
|||
$('#NewItemText').val(''); |
|||
}); |
|||
}); |
|||
}); |
|||
```` |
|||
|
|||
在第一部分中, 我们注册了在待办事项旁边的删除图标的点击事件, 来删除服务器上的相关项目并在UI上显示通知. 此外, 我们会从 DOM 中移除已删除的项目, 所以我们不需要刷新页面. |
|||
|
|||
在第二部分中, 我们在服务器上创建一个新的待办事项. 如果成功, 我们将操纵DOM来将新的 `<li>` 元素插入到待办事项列表. 这样我们不需要在创建新的待办事项后刷新整个页面. |
|||
|
|||
这里有趣的部分是我们如何与服务器通信. 请参阅 *动态JavaScript代理和自动API控制器* 部分来了解其工作原理. 但是现在让我们继续并完成这个应用程序. |
|||
|
|||
### Index.css |
|||
|
|||
最后, 请打开 *TodoApp.Web* 项目的 `Pages` 文件夹中的 `Index.css` 文件, 并替换为以下内容: |
|||
|
|||
````css |
|||
#TodoList{ |
|||
list-style: none; |
|||
margin: 0; |
|||
padding: 0; |
|||
} |
|||
|
|||
#TodoList li { |
|||
padding: 5px; |
|||
margin: 5px 0px; |
|||
border: 1px solid #cccccc; |
|||
background-color: #f5f5f5; |
|||
} |
|||
|
|||
#TodoList li i |
|||
{ |
|||
opacity: 0.5; |
|||
} |
|||
|
|||
#TodoList li i:hover |
|||
{ |
|||
opacity: 1; |
|||
color: #ff0000; |
|||
cursor: pointer; |
|||
} |
|||
```` |
|||
|
|||
这是待办事项页面的简单样式. 我们相信你可以做得更好 :) |
|||
|
|||
现在, 你可以再次运行应用程序并看到结果. |
|||
|
|||
### 动态JavaScript代理和自动API控制器 |
|||
|
|||
在 `Index.js` 文件中, 我们使用了 `todoApp.todo.delete(...)` 和 `todoApp.todo.create(...)` 方法来与服务器通信. 这些函数是由ABP框架动态创建的, 这要归功于 [动态JavaScript客户端代理](../../UI/AspNetCore/Dynamic-JavaScript-Proxies.md) 系统. 它们执行 HTTP API 到服务器调用并返回一个Promise对象, 这样你就可以使用 `then` 函数注册一个回调, 像之前所做的那样. |
|||
|
|||
但是, 你可能会注意到我们还没有创建任何API控制器, 所以服务器如何处理这些请求? 这个问题为我们引出了ABP 框架的 [自动API控制器](../../API/Auto-API-Controllers.md) 功能. 它通过约定自动将应用程序服务转换为 API 控制器. |
|||
|
|||
如果你在应用程序URL中输入 `/swagger` 来打开 [Swagger UI](https://swagger.io/tools/swagger-ui/) , 就会看到Todo API: |
|||
|
|||
 |
|||
|
|||
{{else if UI=="Blazor" || UI=="BlazorServer"}} |
|||
|
|||
### Index.razor.cs |
|||
|
|||
打开在*TodoApp.Blazor*项目的`Pages`文件夹中`Index.razor.cs`文件, 并替换为一下内容: |
|||
|
|||
````csharp |
|||
using Microsoft.AspNetCore.Components; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace TodoApp.Blazor.Pages |
|||
{ |
|||
public partial class Index |
|||
{ |
|||
[Inject] |
|||
private ITodoAppService TodoAppService { get; set; } |
|||
|
|||
private List<TodoItemDto> TodoItems { get; set; } = new List<TodoItemDto>(); |
|||
private string NewTodoText { get; set; } |
|||
|
|||
protected async override Task OnInitializedAsync() |
|||
{ |
|||
TodoItems = await TodoAppService.GetListAsync(); |
|||
} |
|||
|
|||
private async Task Create() |
|||
{ |
|||
var result = await TodoAppService.CreateAsync(NewTodoText); |
|||
TodoItems.Add(result); |
|||
NewTodoText = null; |
|||
} |
|||
|
|||
private async Task Delete(TodoItemDto todoItem) |
|||
{ |
|||
await TodoAppService.DeleteAsync(todoItem.Id); |
|||
await Notify.Info("Deleted the todo item."); |
|||
TodoItems.Remove(todoItem); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
此类使用 `ITodoAppService` 来执行待办事项的操作. 它在创建和删除操作后操纵 `TodoItems` 列表. 这样, 我们不需要从服务器刷新整个待办事项列表. |
|||
|
|||
{{if UI=="Blazor"}} |
|||
|
|||
请参阅下面的 *动态C#代理和自动API控制器* 部分, 来了解我们如何在浏览器上运行的 Blazor 应用程序中使用注入和应用服务接口的! 但是现在让我们继续并完成这个应用程序. |
|||
|
|||
{{end # Blazor}} |
|||
|
|||
### Index.razor |
|||
|
|||
打开在 *TodoApp.Blazor* 项目的`Pages`文件夹中 `Index.razor`文件, 并替换为以下代码块内容: |
|||
|
|||
````xml |
|||
@page "/" |
|||
@inherits TodoAppComponentBase |
|||
<div class="container"> |
|||
<Card> |
|||
<CardHeader> |
|||
<CardTitle> |
|||
TODO LIST |
|||
</CardTitle> |
|||
</CardHeader> |
|||
<CardBody> |
|||
<!-- FORM FOR NEW TODO ITEMS --> |
|||
<form id="NewItemForm" |
|||
@onsubmit:preventDefault |
|||
@onsubmit="() => Create()" |
|||
class="form-inline"> |
|||
<input type="text" |
|||
@bind-value="@NewTodoText" |
|||
class="form-control mr-2" |
|||
placeholder="enter text..."> |
|||
<button type="submit" class="btn btn-primary">Submit</button> |
|||
</form> |
|||
|
|||
<!-- TODO ITEMS LIST --> |
|||
<ul id="TodoList"> |
|||
@foreach (var todoItem in TodoItems) |
|||
{ |
|||
<li data-id="@todoItem.Id"> |
|||
<i class="far fa-trash-alt" |
|||
@onclick="() => Delete(todoItem)" |
|||
></i> @todoItem.Text |
|||
</li> |
|||
} |
|||
</ul> |
|||
</CardBody> |
|||
</Card> |
|||
</div> |
|||
```` |
|||
|
|||
### Index.razor.css |
|||
|
|||
最后, 请打开 *TodoApp.Blazor* 项目的 `Pages` 文件夹中的 `Index.razor.css`文件, 并替换为以下内容: |
|||
|
|||
````css |
|||
#TodoList{ |
|||
list-style: none; |
|||
margin: 0; |
|||
padding: 0; |
|||
} |
|||
|
|||
#TodoList li { |
|||
padding: 5px; |
|||
margin: 5px 0px; |
|||
border: 1px solid #cccccc; |
|||
background-color: #f5f5f5; |
|||
} |
|||
|
|||
#TodoList li i |
|||
{ |
|||
opacity: 0.5; |
|||
} |
|||
|
|||
#TodoList li i:hover |
|||
{ |
|||
opacity: 1; |
|||
color: #ff0000; |
|||
cursor: pointer; |
|||
} |
|||
```` |
|||
|
|||
这是待办事项页面的简单样式. 我们相信你可以做得更好 :) |
|||
|
|||
现在, 你可以再次运行应用程序来查看结果. |
|||
|
|||
{{if UI=="Blazor"}} |
|||
|
|||
### 动态C#代理和自动API控制器 |
|||
|
|||
在 `Index.razor.cs` 文件中, 我们已经注入(使用 `[Inject]` 特性)并像使用本地服务一样使用 `ITodoAppService`. 请记住, Blazor应用程序是在浏览器上运行, 但此应用程序服务的实现却是在服务器上运行的. |
|||
|
|||
这个神奇的过程是由ABP框架的 [动态C#客户端代理](../../API/Dynamic-CSharp-API-Clients.md) 系统完成. 它使用标准的`HttpClient` 并执行HTTP API到远程服务器请求. 它还为我们处理所有标准任务, 包括授权、JSON序列化和异常处理. |
|||
|
|||
但是, 你可能会问我们还没有创建任何API控制器, 那么服务器如何处理这些请求呢? 这个问题为我们引出了ABP 框架的 [自动API控制器](../../API/Auto-API-Controllers.md) 功能. 它通过约定自动将应用程序服务转换为 API 控制器. |
|||
|
|||
如果你运行 `TodoApp.HttpApi.Host` 应用程序, 你可以看到Todo API: |
|||
|
|||
 |
|||
|
|||
{{end # Blazor}} |
|||
|
|||
{{else if UI=="NG"}} |
|||
|
|||
### 服务代理生成 |
|||
|
|||
ABP提供了一个便捷的功能来自动创建客户端服务, 以方便地使用由服务器提供的HTTP API. |
|||
|
|||
你首先需要运行 `TodoApp.HttpApi.Host` 项目, 因为代理生成器从服务器应用程序读取API定义. |
|||
|
|||
> **请注意**: IIS Express有一个问题: 它不允许从另一个进程连接到应用程序. 如果你使用的是Visual Studio, 请在 “运行” 按钮下拉列表中选择`TodoApp.HttpApi.Host` 来替代IIS Express, 如下图所示: |
|||
|
|||
 |
|||
|
|||
当启动 `TodoApp.HttpApi.Host` 项目后, 在`angular`文件夹中打开一个命令行终端并输入以下命令: |
|||
|
|||
````bash |
|||
abp generate-proxy |
|||
```` |
|||
|
|||
如果一切顺利, 它应该生成如下输出: |
|||
|
|||
````bash |
|||
CREATE src/app/proxy/generate-proxy.json (170978 bytes) |
|||
CREATE src/app/proxy/README.md (1000 bytes) |
|||
CREATE src/app/proxy/todo.service.ts (794 bytes) |
|||
CREATE src/app/proxy/models.ts (66 bytes) |
|||
CREATE src/app/proxy/index.ts (58 bytes) |
|||
```` |
|||
|
|||
然后, 我们可以使用 `todoService` 来使用服务器端HTTP API, 就像我们将在下一节中做的那样. |
|||
|
|||
### home.component.ts |
|||
|
|||
打开 `/angular/src/app/home/home.component.ts` 文件, 并用以下代码块替换其内容: |
|||
|
|||
````js |
|||
import { ToasterService } from '@abp/ng.theme.shared'; |
|||
import { Component, OnInit } from '@angular/core'; |
|||
import { TodoItemDto, TodoService } from '@proxy'; |
|||
|
|||
@Component({ |
|||
selector: 'app-home', |
|||
templateUrl: './home.component.html', |
|||
styleUrls: ['./home.component.scss'] |
|||
}) |
|||
export class HomeComponent implements OnInit { |
|||
|
|||
todoItems: TodoItemDto[]; |
|||
newTodoText: string; |
|||
|
|||
constructor( |
|||
private todoService: TodoService, |
|||
private toasterService: ToasterService) |
|||
{ } |
|||
|
|||
ngOnInit(): void { |
|||
this.todoService.getList().subscribe(response => { |
|||
this.todoItems = response; |
|||
}); |
|||
} |
|||
|
|||
create(): void{ |
|||
this.todoService.create(this.newTodoText).subscribe((result) => { |
|||
this.todoItems = this.todoItems.concat(result); |
|||
this.newTodoText = null; |
|||
}); |
|||
} |
|||
|
|||
delete(id: string): void { |
|||
this.todoService.delete(id).subscribe(() => { |
|||
this.todoItems = this.todoItems.filter(item => item.id !== id); |
|||
this.toasterService.info('Deleted the todo item.'); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
```` |
|||
|
|||
我们已经实现了使用 `todoService` 来获取待办事项目列表, 并将返回值赋值给 `todoItems` 数组. 我们还添加了 `create` 和 `delete` 方法. 这些方法将在视图端使用. |
|||
|
|||
### home.component.html |
|||
|
|||
打开 `/angular/src/app/home/home.component.html` 文件, 并用以下代码块替换其内容: |
|||
|
|||
````html |
|||
<div class="container"> |
|||
<div class="card"> |
|||
<div class="card-header"> |
|||
<div class="card-title">TODO LIST</div> |
|||
</div> |
|||
<div class="card-body"> |
|||
<!-- FORM FOR NEW TODO ITEMS --> |
|||
<form class="form-inline" (ngSubmit)="create()"> |
|||
<input |
|||
name="NewTodoText" |
|||
type="text" |
|||
[(ngModel)]="newTodoText" |
|||
class="form-control mr-2" |
|||
placeholder="enter text..." |
|||
/> |
|||
<button type="submit" class="btn btn-primary">Submit</button> |
|||
</form> |
|||
|
|||
<!-- TODO ITEMS LIST --> |
|||
<ul id="TodoList"> |
|||
<li *ngFor="let todoItem of todoItems"> |
|||
<i class="fa fa-trash-o" (click)="delete(todoItem.id)"></i> {%{{{ todoItem.text }}}%} |
|||
</li> |
|||
</ul> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
```` |
|||
|
|||
### home.component.scss |
|||
|
|||
最后, 打开`/angular/src/app/home/home.component.scss` 文件, 并用以下代码块替换其内容: |
|||
|
|||
````css |
|||
#TodoList{ |
|||
list-style: none; |
|||
margin: 0; |
|||
padding: 0; |
|||
} |
|||
|
|||
#TodoList li { |
|||
padding: 5px; |
|||
margin: 5px 0px; |
|||
border: 1px solid #cccccc; |
|||
background-color: #f5f5f5; |
|||
} |
|||
|
|||
#TodoList li i |
|||
{ |
|||
opacity: 0.5; |
|||
} |
|||
|
|||
#TodoList li i:hover |
|||
{ |
|||
opacity: 1; |
|||
color: #ff0000; |
|||
cursor: pointer; |
|||
} |
|||
```` |
|||
|
|||
这是待办事项页面的简单样式. 我们相信你可以做得更好 :) |
|||
|
|||
现在, 你可以再次运行应用程序来查看结果. |
|||
|
|||
{{end}} |
|||
|
|||
## 总结 |
|||
|
|||
在这个教程中, 我们已经建立了一个非常简单的应用程序来熟悉 ABP 框架. 如果你想要构建一个实际场景的应用程序, 请查看 [web 应用程序开发教程](../Part-1.md), 该教程涵盖了真实的 Web 应用程序开发的所有方面. |
|||
|
|||
## 源代码 |
|||
|
|||
你可以在[这里](https://github.com/abpframework/abp-samples/tree/master/TodoApp)找到已完成的项目源代码. |
|||
|
|||
## 另请参见 |
|||
|
|||
* [Web应用程序开发教程](../Part-1.md) |
|||
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
@ -1,822 +0,0 @@ |
|||
# 快速入门 |
|||
|
|||
````json |
|||
//[doc-params] |
|||
{ |
|||
"UI": ["MVC", "Blazor", "BlazorServer", "NG"], |
|||
"DB": ["EF", "Mongo"] |
|||
} |
|||
```` |
|||
|
|||
这是一个单独的部分,使用ABP框架构建简单待办事项应用程序的快速入门教程.这是一个最终应用的截图: |
|||
|
|||
 |
|||
|
|||
你可以在[这里](https://github.com/abpframework/abp-samples/tree/master/TodoApp)找到已完成的项目源代码. |
|||
|
|||
## 先决条件 |
|||
|
|||
* 一个集成开发环境 (比如: [Visual Studio](https://visualstudio.microsoft.com/vs/)) 它需要支持 [.NET 5.0+](https://dotnet.microsoft.com/download/dotnet) 的开发. |
|||
|
|||
{{if DB=="Mongo"}} |
|||
|
|||
* [MongoDB Server 4.0+](https://docs.mongodb.com/manual/administration/install-community/) |
|||
|
|||
{{end}} |
|||
|
|||
{{if UI=="NG"}} |
|||
|
|||
* [Node v14.x](https://nodejs.org/) |
|||
|
|||
{{end}} |
|||
|
|||
## 创建新的解决方案 |
|||
|
|||
我们将使用[ABP CLI](../../CLI.md) 创建带有ABP框架的新解决方案. 你可以在命令行终端中运行以下命令来安装它: |
|||
|
|||
````bash |
|||
dotnet tool install -g Volo.Abp.Cli |
|||
```` |
|||
|
|||
然后创建一个空文件夹,打开命令行终端并以打开的文件夹为路径在终端中执行以下命令: |
|||
|
|||
````bash |
|||
abp new TodoApp{{if UI=="Blazor"}} -u blazor{{else if UI=="BlazorServer"}} -u blazor-server{{else if UI=="NG"}} -u angular{{end}}{{if DB=="Mongo"}} -d mongodb{{end}} |
|||
```` |
|||
|
|||
{{if UI=="NG"}} |
|||
|
|||
这将创建一个名为*TodoApp*的新解决方案,其中包含`angular`和`aspnet core`文件夹.解决方案就绪后,在你的IDE中打开ASP.NET Core 解决方案. |
|||
|
|||
{{else}} |
|||
|
|||
这将创建一个名为*TodoApp*的新解决方案.解决方案准备好后,在你的IDE中打开它. |
|||
|
|||
{{end}} |
|||
|
|||
### 创建数据库 |
|||
|
|||
如果你使用的是visual studio,请右键单击`TodoApp.DbMigrator`项目,选择*设置为启动项目*,然后按*Ctrl+F5*运行它而不进行调试.它将创建初始数据库并播种初始数据. |
|||
|
|||
{{if DB=="EF"}} |
|||
|
|||
> 由于*DbMigrator*添加初始迁移并重新编译项目,一些ide(例如Rider)在第一次运行时可能会出现问题.在这种情况下,请打开`.DbMigrator`项目文件夹中的命令行终端,然后执行`dotnet run`命令. |
|||
|
|||
{{end}} |
|||
|
|||
### 运行应用程序 |
|||
|
|||
{{if UI=="MVC" || UI=="BlazorServer"}} |
|||
|
|||
最好在开始开发之前运行一下应用程序.确保 {{if UI=="BlazorServer"}}`TodoApp.Blazor`{{else}}`TodoApp.Web`{{end}} 是启动项目,然后运行应用程序(Visual Studio中是Ctrl+F5)以查看查看初始UI: |
|||
|
|||
{{else if UI=="Blazor"}} |
|||
|
|||
最好在开始开发之前运行一下应用程序.该解决方案有两个主要应用: |
|||
|
|||
* `TodoApp.HttpApi.Host` 托管服务器端的 HTTP API. |
|||
* `TodoApp.Blazor` 客户端托管的 Blazor WebAssembly 应用. |
|||
|
|||
确保 `TodoApp.HttpApi.Host` 是启动项目,然后运行应用程序(Visual Studio中的Ctrl+F5)以查看[Swagger UI](https://swagger.io/tools/swagger-ui/)上server-side 的 HTTP API: |
|||
|
|||
 |
|||
|
|||
你可以使用此UI查看和测试你的HTTP API.现在,我们可以将 `TodoApp.Blazor` 设置为启动项目,并运行它来打开实际的Blazor应用程序UI: |
|||
|
|||
{{else if UI=="NG"}} |
|||
|
|||
最好在开始开发之前运行应用程序.该解决方案有两个主要应用: |
|||
|
|||
* `TodoApp.HttpApi.Host` (在.NET解决方案中)承载服务器端HTTP API. |
|||
* `angular` 文件夹包含 Angular 应用程序. |
|||
|
|||
确保`TodoApp.HttpApi.Host`project是启动项目,然后运行应用程序(visual studio中是Ctrl+F5)以查看[Swagger UI](https://swagger.io/tools/swagger-ui/)上server-side HTTP API: |
|||
|
|||
 |
|||
|
|||
你可以使用这个UI查看和测试你的HTTP API.如果可以的话,我们可以运行Angular客户端应用程序. |
|||
|
|||
首先,运行以下命令来还原NPM包: |
|||
|
|||
````bash |
|||
npm install |
|||
```` |
|||
|
|||
安装所有软件包需要一些时间.然后可以使用以下命令运行应用程序: |
|||
|
|||
````bash |
|||
npm start |
|||
```` |
|||
|
|||
此命令需要一点时间,但最终会在默认浏览器中运行并打开应用程序: |
|||
|
|||
{{end}} |
|||
|
|||
 |
|||
|
|||
你可以单击 *登录* 按钮,以`admin`作为用户名,`1q2w3E*` 作为密码登录到应用程序. |
|||
|
|||
一切就绪.我们可以开始编码了! |
|||
|
|||
## 领域层 |
|||
|
|||
此应用程序只有一个 [实体](../../Entities.md) 我们开始创建它. 在*TodoApp.Domain*项目中创建一个新的 `TodoItem` 类: |
|||
|
|||
````csharp |
|||
using System; |
|||
using Volo.Abp.Domain.Entities; |
|||
|
|||
namespace TodoApp |
|||
{ |
|||
public class TodoItem : BasicAggregateRoot<Guid> |
|||
{ |
|||
public string Text { get; set; } |
|||
} |
|||
} |
|||
```` |
|||
|
|||
`BasicAggregateRoot` 是创建根实体的最简单的基类之一,`Guid` 是这里实体的主键 (`Id`). |
|||
|
|||
## Database Integration 数据库集成 |
|||
|
|||
{{if DB=="EF"}} |
|||
|
|||
下一步是配置 [Entity Framework Core](../../Entity-Framework-Core.md). |
|||
|
|||
### Mapping Configuration 映射配置 |
|||
|
|||
在 *TodoApp.EntityFrameworkCore* 项目的 `EntityFrameworkCore` 文件夹中打开 `TodoAppDbContext` 类,并向该类添加一个新的 `DbSet` 属性: |
|||
|
|||
````csharp |
|||
public DbSet<TodoItem> TodoItems { get; set; } |
|||
```` |
|||
|
|||
然后在同一文件夹中打开 `TodoAppDbContextModelCreatingExtensions` 类,并为 `TodoItem` 类添加映射配置,如下所示: |
|||
|
|||
````csharp |
|||
public static void ConfigureTodoApp(this ModelBuilder builder) |
|||
{ |
|||
Check.NotNull(builder, nameof(builder)); |
|||
|
|||
builder.Entity<TodoItem>(b => |
|||
{ |
|||
b.ToTable("TodoItems"); |
|||
}); |
|||
} |
|||
```` |
|||
|
|||
我们已经将 `TodoItem` 实体映射到数据库中的 `TodoItems` 表. |
|||
|
|||
### 代码优先迁移 |
|||
|
|||
解决方案启动模版已经配置为使用Entity Framework Core [Code First 迁移](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations).由于我们已经更改了数据库映射配置,因此我们应该创建一个新的迁移并将更改应用于数据库. |
|||
|
|||
在 *TodoApp.EntityFrameworkCore.DbMigrations* 项目的目录中打开一个命令行终端,然后键入以下命令: |
|||
|
|||
````bash |
|||
dotnet ef migrations add Added_TodoItem |
|||
```` |
|||
|
|||
这将向项目添加一个新的迁移类: |
|||
|
|||
 |
|||
|
|||
你可以在同一命令行终端中使用以下命令将更改应用于数据库: |
|||
|
|||
````bash |
|||
dotnet ef database update |
|||
```` |
|||
|
|||
> 如果你使用的是Visual Studio,*则可能希望在包管理器控制台 (PMC)* 中使用 `Add-Migration Added_TodoItem` 和 `Update-Database` 命令.在这种情况下,请确保 {{if UI=="MVC"}}`TodoApp.Web`{{else if UI=="BlazorServer"}}`TodoApp.Blazor`{{else if UI=="Blazor" || UI=="NG"}}`TodoApp.HttpApi.Host`{{end}} 是启动项目,并且 `TodoApp.EntityFrameworkCore.DbMigrations` 是PMC中的 *默认项目*. |
|||
{{else if DB=="Mongo"}} |
|||
|
|||
下一步是设置 [MongoDB](../MongoDB.md) 配置.在 *TodoApp.MongoDB* 项目的 `MongoDb` 文件夹中打开 `TodoAppMongoDbContext` 类,并进行以下更改; |
|||
|
|||
1. 向类添加新属性: |
|||
|
|||
````csharp |
|||
public IMongoCollection<TodoItem> TodoItems => Collection<TodoItem>(); |
|||
```` |
|||
|
|||
2. 在 `CreateModel` 方法中添加以下代码: |
|||
|
|||
````csharp |
|||
modelBuilder.Entity<TodoItem>(b => |
|||
{ |
|||
b.CollectionName = "TodoItems"; |
|||
}); |
|||
```` |
|||
|
|||
{{end}} |
|||
|
|||
现在,我们可以使用ABP仓储来保存和检索待办事项列表项了,就像我们将在下一节中做的那样. |
|||
|
|||
## 应用层 |
|||
|
|||
[应用服务](../../Application-Services.md) 用于执行应用程序的用例.我们需要执行以下用例; |
|||
|
|||
* 获取待办事项列表 |
|||
* 创建新的待办事项 |
|||
* 删除现有的待办事项 |
|||
|
|||
### 应用服务接口 |
|||
|
|||
我们可以从为应用程序服务定义一个接口开始.在 *TodoApp.Application.Contracts* 项目中创建一个新的 `ITodoAppService` 接口,如下所示: |
|||
|
|||
````csharp |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace TodoApp |
|||
{ |
|||
public interface ITodoAppService : IApplicationService |
|||
{ |
|||
Task<List<TodoItemDto>> GetListAsync(); |
|||
Task<TodoItemDto> CreateAsync(string text); |
|||
Task DeleteAsync(Guid id); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
### 数据传输对象 |
|||
|
|||
`GetListAsync` 和 `CreateAsync` 方法返回 `TodoItemDto`. 通常应用服务获取或返回的是 DTO ([数据传输对象](../../Data-Transfer-Objects.md)) 而不是直接获取或返回实体对象. 所以,我们应该在此定义 DTO(数据传输对象) 类. 在 *TodoApp.Application.Contracts* 项目中创建一个名为 `TodoItemDto` 的类: |
|||
|
|||
````csharp |
|||
using System; |
|||
|
|||
namespace TodoApp |
|||
{ |
|||
public class TodoItemDto |
|||
{ |
|||
public Guid Id { get; set; } |
|||
public string Text { get; set; } |
|||
} |
|||
} |
|||
```` |
|||
|
|||
这是一个非常简单的DTO类,与我们的 `TodoItem` 实体相对应.我们接下来实现 `ITodoAppService` 接口. |
|||
|
|||
### 应用服务实现 |
|||
|
|||
在 *TodoApp.Application* 项目内部创建一个 `TodoAppService` 类,如下所示: |
|||
|
|||
````csharp |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Services; |
|||
using Volo.Abp.Domain.Repositories; |
|||
|
|||
namespace TodoApp |
|||
{ |
|||
public class TodoAppService : ApplicationService, ITodoAppService |
|||
{ |
|||
private readonly IRepository<TodoItem, Guid> _todoItemRepository; |
|||
|
|||
public TodoAppService(IRepository<TodoItem, Guid> todoItemRepository) |
|||
{ |
|||
_todoItemRepository = todoItemRepository; |
|||
} |
|||
|
|||
// TODO: Implement the methods here... |
|||
} |
|||
} |
|||
```` |
|||
|
|||
该类继承自ABP框架的 `ApplicationService` 类,并实现了之前定义的 `ITodoAppService`接口.ABP为实体提供默认的通用的 [仓储](../repositories.md).我们可以使用它们来执行基本的数据库操作. 这个类 [注入](../Dependency-Injection.md) `IRepository<TodoItem, Guid>` 它是 `TodoItem` 实体的默认存储库.我们将使用它来实现之前描述的用例. |
|||
|
|||
#### 获取所有待办事项列表 |
|||
|
|||
让我们开始实现 `GetListAsync` 方法: |
|||
|
|||
````csharp |
|||
public async Task<List<TodoItemDto>> GetListAsync() |
|||
{ |
|||
var items = await _todoItemRepository.GetListAsync(); |
|||
return items |
|||
.Select(item => new TodoItemDto |
|||
{ |
|||
Id = item.Id, |
|||
Text = item.Text |
|||
}).ToList(); |
|||
} |
|||
```` |
|||
|
|||
我们只是简单的从数据库中获取完整的 `TodoItem` 列表,将它们映射到 `TodoItemDto` 对象并作为结果返回. |
|||
|
|||
#### 创建一个新的待办事项 |
|||
|
|||
下一个方法是 `CreateAsync`,我们可以像如下所示来实现: |
|||
|
|||
````csharp |
|||
public async Task<TodoItemDto> CreateAsync(string text) |
|||
{ |
|||
var todoItem = await _todoItemRepository.InsertAsync( |
|||
new TodoItem {Text = text} |
|||
); |
|||
|
|||
return new TodoItemDto |
|||
{ |
|||
Id = todoItem.Id, |
|||
Text = todoItem.Text |
|||
}; |
|||
} |
|||
```` |
|||
|
|||
仓储的 `InsertAsync` 方法将给定的 `TodoItem` 插入数据库,并返回相同的 `TodoItem` 对象.它还设置了 `Id`,因此我们可以在返回对象上使用它.我们只是通过从新的 `TodoItem` 实体创建和返回 `TodoItemDto`. |
|||
|
|||
#### 删除待办事项 |
|||
|
|||
最后,我们可以将 `DeleteAsync` 实现为以下代码块: |
|||
|
|||
````csharp |
|||
public async Task DeleteAsync(Guid id) |
|||
{ |
|||
await _todoItemRepository.DeleteAsync(id); |
|||
} |
|||
```` |
|||
|
|||
至此,应用程序服务已准备好了让UI层来使用. |
|||
|
|||
## 用户界面层 |
|||
|
|||
是时候在UI上显示待办事项了!在开始编写代码之前,最好记住我们正在尝试构建的内容.这是最终UI的示例屏幕截图: |
|||
|
|||
 |
|||
|
|||
> **我们将在本教程中保持最简洁轻量的UI端,以使本教程简单且重点突出.请参阅[web应用程序开发教程](../Part-1.md) 以构建包含各个方面的真实页面.** |
|||
|
|||
{{if UI=="MVC"}} |
|||
|
|||
### Index.cshtml.cs |
|||
|
|||
在 *TodoApp.Web* 项目的 `Pages` 文件夹中打开 `Index.cshtml.cs` 文件,并用以下代码块替换它的默认内容: |
|||
|
|||
````csharp |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace TodoApp.Web.Pages |
|||
{ |
|||
public class IndexModel : TodoAppPageModel |
|||
{ |
|||
public List<TodoItemDto> TodoItems { get; set; } |
|||
|
|||
private readonly ITodoAppService _todoAppService; |
|||
|
|||
public IndexModel(ITodoAppService todoAppService) |
|||
{ |
|||
_todoAppService = todoAppService; |
|||
} |
|||
|
|||
public async Task OnGetAsync() |
|||
{ |
|||
TodoItems = await _todoAppService.GetListAsync(); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
此类使用 `ITodoAppService` 获取待办事项目列表并将它赋值给 `TodoItems` 属性.我们将用它来渲染razor页面上的待办事项目列表. |
|||
|
|||
### Index.cshtml |
|||
|
|||
在 *TodoApp.Web* 项目的 `Pages` 文件夹中打开 `Index.cshtml` 文件,并替换为以下内容: |
|||
|
|||
````xml |
|||
@page |
|||
@model TodoApp.Web.Pages.IndexModel |
|||
@section styles { |
|||
<abp-style src="/Pages/Index.css" /> |
|||
} |
|||
@section scripts { |
|||
<abp-script src="/Pages/Index.js" /> |
|||
} |
|||
<div class="container"> |
|||
<abp-card> |
|||
<abp-card-header> |
|||
<abp-card-title> |
|||
TODO LIST |
|||
</abp-card-title> |
|||
</abp-card-header> |
|||
<abp-card-body> |
|||
<!-- FORM FOR NEW TODO ITEMS --> |
|||
<form id="NewItemForm" class="form-inline"> |
|||
<input id="NewItemText" |
|||
type="text" |
|||
class="form-control mr-2" |
|||
placeholder="enter text..."> |
|||
<button type="submit" class="btn btn-primary">Submit</button> |
|||
</form> |
|||
|
|||
<!-- TODO ITEMS LIST --> |
|||
<ul id="TodoList"> |
|||
@foreach (var todoItem in Model.TodoItems) |
|||
{ |
|||
<li data-id="@todoItem.Id"> |
|||
<i class="fa fa-trash-o"></i> @todoItem.Text |
|||
</li> |
|||
} |
|||
</ul> |
|||
</abp-card-body> |
|||
</abp-card> |
|||
</div> |
|||
```` |
|||
|
|||
我们使用ABP的 [卡片标签助手](../../UI/AspNetCore/Tag-Helpers/Cards.md) 创建一个简单的卡片视图.你可以直接使用标准的bootstrap HTML, 不过ABP的[标签助手](../../UI/AspNetCore/Tag-Helpers/Index.md) 使它更简单并且类型安全. |
|||
|
|||
这个页面要导入一个CSS和一个JavaScript文件,所以我们也应该创建它们. |
|||
|
|||
### Index.js |
|||
|
|||
在 *TodoApp.Web* 项目的 `Pages` 文件夹中打开 `Index.js` 文件,并替换为以下内容: |
|||
|
|||
````js |
|||
$(function () { |
|||
|
|||
// DELETING ITEMS ///////////////////////////////////////// |
|||
$('#TodoList').on('click', 'li i', function(){ |
|||
var $li = $(this).parent(); |
|||
var id = $li.attr('data-id'); |
|||
|
|||
todoApp.todo.delete(id).then(function(){ |
|||
$li.remove(); |
|||
abp.notify.info('Deleted the todo item.'); |
|||
}); |
|||
}); |
|||
|
|||
// CREATING NEW ITEMS ///////////////////////////////////// |
|||
$('#NewItemForm').submit(function(e){ |
|||
e.preventDefault(); |
|||
|
|||
var todoText = $('#NewItemText').val(); |
|||
todoApp.todo.create(todoText).then(function(result){ |
|||
$('<li data-id="' + result.id + '">') |
|||
.html('<i class="fa fa-trash-o"></i> ' + result.text) |
|||
.appendTo($('#TodoList')); |
|||
$('#NewItemText').val(''); |
|||
}); |
|||
}); |
|||
}); |
|||
```` |
|||
|
|||
在第一部分中,我们在待办事项目附近注册点击删除图标所要触发的事件,删除服务器上的相关待办事项并在UI上显示通知.另外,我们从DOM中删除已删除的项,因此我们不需要刷新页面. |
|||
|
|||
在第二部分中,我们在服务器上创建一个新的待办事项.如果成功,我们将操纵DOM以将新的 `<li>` 元素插入todo列表.这样,创建新的待办事项后就不用刷新整个页面了. |
|||
|
|||
这里有趣的部分是我们如何与服务器通信.请参阅 *动态JavaScript代理和自动API控制器* 部分,以了解其工作原理.但是现在让我们继续并完成这个应用程序. |
|||
|
|||
### Index.css |
|||
|
|||
来到最后要做的工作,请打开 *TodoApp.Web* 项目的 `Pages` 文件夹中的 `Index.css` 文件,并替换为以下内容: |
|||
|
|||
````css |
|||
#TodoList{ |
|||
list-style: none; |
|||
margin: 0; |
|||
padding: 0; |
|||
} |
|||
|
|||
#TodoList li { |
|||
padding: 5px; |
|||
margin: 5px 0px; |
|||
border: 1px solid #cccccc; |
|||
background-color: #f5f5f5; |
|||
} |
|||
|
|||
#TodoList li i |
|||
{ |
|||
opacity: 0.5; |
|||
} |
|||
|
|||
#TodoList li i:hover |
|||
{ |
|||
opacity: 1; |
|||
color: #ff0000; |
|||
cursor: pointer; |
|||
} |
|||
```` |
|||
|
|||
这是待办事项页面的简单样式.我们相信你可以做得更好 :) |
|||
|
|||
现在,你可以再次运行应用程序以查看结果. |
|||
|
|||
### Dynamic JavaScript Proxies & Auto API Controllers 动态JavaScript代理和自动应用编程接口控制器 |
|||
|
|||
在 `Index.js` 文件中,我们使用了 `todoApp.todo.delete(...)` 和 `todoApp.todo.create(...)` 函数与服务器通信.这些函数是由ABP框架动态创建的,这要归功于 [动态JavaScript客户端代理](../UI/AspNetCore/Dynamic-JavaScript-Proxies.md) 系统.他们对服务器执行HTTP API调用并返回承诺,因此你可以像上面所做的那样注册对 `then` 函数的回调. |
|||
|
|||
但是,你可能会问我们还没有创建任何API控制器,那么服务器如何处理这些请求呢? 这个问题为我们引出了ABP框架的 [自动API控制器](../API/Auto-API-Controllers.md) 功能.它通过约定自动将应用程序服务转换为API Controllers(API控制器). |
|||
|
|||
如果通过在应用程序中输入 `/swagger` URL来打开 [swagger UI](https://swagger.io/tools/ Swagger-ui/),则可以看到Todo API: |
|||
|
|||
 |
|||
|
|||
{{else if UI=="Blazor" || UI=="BlazorServer"}} |
|||
|
|||
### Index.razor.cs |
|||
|
|||
在 *TodoApp.Blazor* 项目的 `Pages` 文件夹中打开 `Index.razor.cs` 文件,并用以下代码块替换内容: |
|||
|
|||
````csharp |
|||
using Microsoft.AspNetCore.Components; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace TodoApp.Blazor.Pages |
|||
{ |
|||
public partial class Index |
|||
{ |
|||
[Inject] |
|||
private ITodoAppService TodoAppService { get; set; } |
|||
|
|||
private List<TodoItemDto> TodoItems { get; set; } = new List<TodoItemDto>(); |
|||
private string NewTodoText { get; set; } |
|||
|
|||
protected async override Task OnInitializedAsync() |
|||
{ |
|||
TodoItems = await TodoAppService.GetListAsync(); |
|||
} |
|||
|
|||
private async Task Create() |
|||
{ |
|||
var result = await TodoAppService.CreateAsync(NewTodoText); |
|||
TodoItems.Add(result); |
|||
NewTodoText = null; |
|||
} |
|||
|
|||
private async Task Delete(TodoItemDto todoItem) |
|||
{ |
|||
await TodoAppService.DeleteAsync(todoItem.Id); |
|||
await Notify.Info("Deleted the todo item."); |
|||
TodoItems.Remove(todoItem); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
这个类使用 `ITodoAppService` 对待办事项执行操作.它在创建和删除操作后操纵 `TodoItems` 列表.这样,我们不需要从服务器刷新整个todo列表. |
|||
|
|||
{{if UI=="Blazor"}} |
|||
|
|||
请参阅下面的 *动态C # 代理和自动API控制器* 部分,了解如何从浏览器上运行的Blazor应用程序注入和使用应用程序服务接口!但是现在,让我们继续并完成这个应用. |
|||
|
|||
{{end # Blazor}} |
|||
|
|||
### Index.razor |
|||
|
|||
在 *TodoApp.Blazor* 项目的 `Pages` 文件夹中打开 `Index.razor` 文件,并用以下代码块替换内容: |
|||
|
|||
````xml |
|||
@page "/" |
|||
@inherits TodoAppComponentBase |
|||
<div class="container"> |
|||
<Card> |
|||
<CardHeader> |
|||
<CardTitle> |
|||
TODO LIST |
|||
</CardTitle> |
|||
</CardHeader> |
|||
<CardBody> |
|||
<!-- FORM FOR NEW TODO ITEMS --> |
|||
<form id="NewItemForm" |
|||
@onsubmit:preventDefault |
|||
@onsubmit="() => Create()" |
|||
class="form-inline"> |
|||
<input type="text" |
|||
@bind-value="@NewTodoText" |
|||
class="form-control mr-2" |
|||
placeholder="enter text..."> |
|||
<button type="submit" class="btn btn-primary">Submit</button> |
|||
</form> |
|||
|
|||
<!-- TODO ITEMS LIST --> |
|||
<ul id="TodoList"> |
|||
@foreach (var todoItem in TodoItems) |
|||
{ |
|||
<li data-id="@todoItem.Id"> |
|||
<i class="far fa-trash-alt" |
|||
@onclick="() => Delete(todoItem)" |
|||
></i> @todoItem.Text |
|||
</li> |
|||
} |
|||
</ul> |
|||
</CardBody> |
|||
</Card> |
|||
</div> |
|||
```` |
|||
|
|||
### Index.razor.css |
|||
|
|||
最后,请在 *TodoApp.Web* 项目的 `Pages` 文件夹中打开 `Index.razor.css` 文件,并替换为以下内容: |
|||
|
|||
````css |
|||
#TodoList{ |
|||
list-style: none; |
|||
margin: 0; |
|||
padding: 0; |
|||
} |
|||
|
|||
#TodoList li { |
|||
padding: 5px; |
|||
margin: 5px 0px; |
|||
border: 1px solid #cccccc; |
|||
background-color: #f5f5f5; |
|||
} |
|||
|
|||
#TodoList li i |
|||
{ |
|||
opacity: 0.5; |
|||
} |
|||
|
|||
#TodoList li i:hover |
|||
{ |
|||
opacity: 1; |
|||
color: #ff0000; |
|||
cursor: pointer; |
|||
} |
|||
```` |
|||
|
|||
这是待办事项页面的简单样式.我们相信你可以做得更好 :) |
|||
|
|||
现在,你可以再次运行应用程序以查看结果. |
|||
|
|||
{{if UI=="Blazor"}} |
|||
|
|||
### Dynamic C# Proxies & Auto API Controllers 动态C#代理和自动应用编程接口控制器 |
|||
|
|||
在 `Index.razor.cs` 文件中,我们已经注入(使用 `[Inject]` 特性)并像使用本地服务一样使用 `ITodoAppService`.请记住,当此应用程序服务的实例在服务器上运行的同时,Blazor应用程序正在浏览器上运行. |
|||
|
|||
这个神奇的过程是由ABP框架的 [动态C#客户端代理](../API/Dynamic-CSharp-API-Clients.md) 系统完成的.它使用标准的 `HttpClient` 并向远程服务器执行HTTP API请求.它还为我们处理所有标准任务,包括授权,JSON序列化和异常处理. |
|||
|
|||
但是,你可能会问我们还没有创建任何API控制器,那么服务器如何处理这些请求呢? 这个问题为我们引出了ABP框架的 [自动API控制器](../API/Auto-API-Controllers.md) 功能.它通过约定自动将应用程序服务转换为API控制器. |
|||
|
|||
如果你运行 `TodoApp.HttpApi.Host` 应用程序,你可以看到Todo API: |
|||
|
|||
 |
|||
|
|||
{{end # Blazor}} |
|||
|
|||
{{else if UI=="NG"}} |
|||
|
|||
### 服务代理生成 |
|||
|
|||
ABP提供了一个方便的功能,可以自动创建客户端服务,轻松使用服务器提供的HTTP APIs. |
|||
|
|||
你首先需要运行 `TodoApp.HttpApi.Host` 项目,因为代理生成器从服务器应用程序读取API定义. |
|||
|
|||
> **请注意**: IIS Express有问题; 它不允许从另一个进程连接到应用程序.如果你使用的是Visual Studio,请在 “运行” 按钮下拉列表中选择 `TodoApp.HttpApi.Host` 而不是IIS Express,如下图所示: |
|||
 |
|||
|
|||
运行 `TodoApp.HttpApi.Host` 项目后,在 `angular` 文件夹中打开命令行终端,然后键入以下命令: |
|||
|
|||
````bash |
|||
abp generate-proxy |
|||
```` |
|||
|
|||
如果一切顺利,应该生成如下所示的输出: |
|||
|
|||
````bash |
|||
CREATE src/app/proxy/generate-proxy.json (170978 bytes) |
|||
CREATE src/app/proxy/README.md (1000 bytes) |
|||
CREATE src/app/proxy/todo.service.ts (794 bytes) |
|||
CREATE src/app/proxy/models.ts (66 bytes) |
|||
CREATE src/app/proxy/index.ts (58 bytes) |
|||
```` |
|||
|
|||
然后,我们可以使用 `todoService` 来使用服务器端HTTP APIs,就像我们将在下一节中做的那样. |
|||
|
|||
### home.component.ts |
|||
|
|||
打开 `/angular/src/app/home/home.component.ts` 文件,并用以下代码块替换其内容: |
|||
|
|||
````js |
|||
import { ToasterService } from '@abp/ng.theme.shared'; |
|||
import { Component, OnInit } from '@angular/core'; |
|||
import { TodoItemDto, TodoService } from '@proxy'; |
|||
|
|||
@Component({ |
|||
selector: 'app-home', |
|||
templateUrl: './home.component.html', |
|||
styleUrls: ['./home.component.scss'] |
|||
}) |
|||
export class HomeComponent implements OnInit { |
|||
|
|||
todoItems: TodoItemDto[]; |
|||
newTodoText: string; |
|||
|
|||
constructor( |
|||
private todoService: TodoService, |
|||
private toasterService: ToasterService) |
|||
{ } |
|||
|
|||
ngOnInit(): void { |
|||
this.todoService.getList().subscribe(response => { |
|||
this.todoItems = response; |
|||
}); |
|||
} |
|||
|
|||
create(): void{ |
|||
this.todoService.create(this.newTodoText).subscribe((result) => { |
|||
this.todoItems = this.todoItems.concat(result); |
|||
this.newTodoText = null; |
|||
}); |
|||
} |
|||
|
|||
delete(id: string): void { |
|||
this.todoService.delete(id).subscribe(() => { |
|||
this.todoItems = this.todoItems.filter(item => item.id !== id); |
|||
this.toasterService.info('Deleted the todo item.'); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
```` |
|||
|
|||
我们已经实现了使用 `todoService` 来获取待办事项目列表,并将返回值赋值给 `todoItems` 数组.我们还添加了 `create`和 `delete` 方法.这些方法将在视图端使用. |
|||
|
|||
### home.component.html |
|||
|
|||
打开 `/angular/src/app/home/home.component.html` 文件,并用以下代码块替换其内容: |
|||
|
|||
````html |
|||
<div class="container"> |
|||
<div class="card"> |
|||
<div class="card-header"> |
|||
<div class="card-title">TODO LIST</div> |
|||
</div> |
|||
<div class="card-body"> |
|||
<!-- FORM FOR NEW TODO ITEMS --> |
|||
<form class="form-inline" (ngSubmit)="create()"> |
|||
<input |
|||
name="NewTodoText" |
|||
type="text" |
|||
[(ngModel)]="newTodoText" |
|||
class="form-control mr-2" |
|||
placeholder="enter text..." |
|||
/> |
|||
<button type="submit" class="btn btn-primary">Submit</button> |
|||
</form> |
|||
|
|||
<!-- TODO ITEMS LIST --> |
|||
<ul id="TodoList"> |
|||
<li *ngFor="let todoItem of todoItems"> |
|||
<i class="fa fa-trash-o" (click)="delete(todoItem.id)"></i> {%{{{ todoItem.text }}}%} |
|||
</li> |
|||
</ul> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
```` |
|||
|
|||
### home.component.scss |
|||
|
|||
最后的最后,打开 `/angular/src/app/home/home.component.scss` 文件,并用以下代码块替换其内容: |
|||
|
|||
````css |
|||
#TodoList{ |
|||
list-style: none; |
|||
margin: 0; |
|||
padding: 0; |
|||
} |
|||
|
|||
#TodoList li { |
|||
padding: 5px; |
|||
margin: 5px 0px; |
|||
border: 1px solid #cccccc; |
|||
background-color: #f5f5f5; |
|||
} |
|||
|
|||
#TodoList li i |
|||
{ |
|||
opacity: 0.5; |
|||
} |
|||
|
|||
#TodoList li i:hover |
|||
{ |
|||
opacity: 1; |
|||
color: #ff0000; |
|||
cursor: pointer; |
|||
} |
|||
```` |
|||
|
|||
这是待办事项页面的简单样式.我们相信你可以做得更好 :) |
|||
|
|||
现在,你可以再次运行应用程序以查看结果. |
|||
|
|||
{{end}} |
|||
|
|||
## 总结 |
|||
|
|||
在本教程中,我们构建了一个非常简单的应用程序来抢先探究ABP框架的新特性.如果你想构建一个实际场景的应用程序,请查看 [应用程序开发教程](../Part-1.md),其中涵盖了实际web应用程序开发的所有方面. |
|||
|
|||
## 源代码 |
|||
|
|||
你可以在[这里](https://github.com/abpframework/abp-samples/tree/master/TodoApp)获取到完整的应用程序源代码. |
|||
|
|||
## 另请参见 |
|||
|
|||
* [Web应用程序开发教程](../Part-1.md) |
|||
|
Before Width: | Height: | Size: 9.3 KiB |
@ -1,27 +0,0 @@ |
|||
{ |
|||
"iisSettings": { |
|||
"windowsAuthentication": false, |
|||
"anonymousAuthentication": true, |
|||
"iisExpress": { |
|||
"applicationUrl": "http://localhost:53601/", |
|||
"sslPort": 44330 |
|||
} |
|||
}, |
|||
"profiles": { |
|||
"IIS Express": { |
|||
"commandName": "IISExpress", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
} |
|||
}, |
|||
"Volo.Abp.AspNetCore.Authentication.OAuth": { |
|||
"commandName": "Project", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
}, |
|||
"applicationUrl": "https://localhost:5001;http://localhost:5000" |
|||
} |
|||
} |
|||
} |
|||
@ -1,27 +0,0 @@ |
|||
{ |
|||
"iisSettings": { |
|||
"windowsAuthentication": false, |
|||
"anonymousAuthentication": true, |
|||
"iisExpress": { |
|||
"applicationUrl": "http://localhost:53603/", |
|||
"sslPort": 44305 |
|||
} |
|||
}, |
|||
"profiles": { |
|||
"IIS Express": { |
|||
"commandName": "IISExpress", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
} |
|||
}, |
|||
"Volo.Abp.AspNetCore.Mvc.Client": { |
|||
"commandName": "Project", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
}, |
|||
"applicationUrl": "https://localhost:5001;http://localhost:5000" |
|||
} |
|||
} |
|||
} |
|||
@ -1,27 +0,0 @@ |
|||
{ |
|||
"iisSettings": { |
|||
"windowsAuthentication": false, |
|||
"anonymousAuthentication": true, |
|||
"iisExpress": { |
|||
"applicationUrl": "http://localhost:53606/", |
|||
"sslPort": 44319 |
|||
} |
|||
}, |
|||
"profiles": { |
|||
"IIS Express": { |
|||
"commandName": "IISExpress", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
} |
|||
}, |
|||
"Volo.Abp.AspNetCore.Serilog": { |
|||
"commandName": "Project", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
}, |
|||
"applicationUrl": "https://localhost:5001;http://localhost:5000" |
|||
} |
|||
} |
|||
} |
|||
@ -1,27 +0,0 @@ |
|||
{ |
|||
"iisSettings": { |
|||
"windowsAuthentication": false, |
|||
"anonymousAuthentication": true, |
|||
"iisExpress": { |
|||
"applicationUrl": "http://localhost:53611/", |
|||
"sslPort": 44331 |
|||
} |
|||
}, |
|||
"profiles": { |
|||
"IIS Express": { |
|||
"commandName": "IISExpress", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
} |
|||
}, |
|||
"Volo.Abp.AspNetCore.SignalR": { |
|||
"commandName": "Project", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
}, |
|||
"applicationUrl": "https://localhost:5001;http://localhost:5000" |
|||
} |
|||
} |
|||
} |
|||
@ -1,27 +0,0 @@ |
|||
{ |
|||
"iisSettings": { |
|||
"windowsAuthentication": false, |
|||
"anonymousAuthentication": true, |
|||
"iisExpress": { |
|||
"applicationUrl": "http://localhost:53599/", |
|||
"sslPort": 44358 |
|||
} |
|||
}, |
|||
"profiles": { |
|||
"IIS Express": { |
|||
"commandName": "IISExpress", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
} |
|||
}, |
|||
"Volo.Abp.AspNetCore.TestBase": { |
|||
"commandName": "Project", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
}, |
|||
"applicationUrl": "https://localhost:5001;http://localhost:5000" |
|||
} |
|||
} |
|||
} |
|||
@ -1,27 +0,0 @@ |
|||
{ |
|||
"iisSettings": { |
|||
"windowsAuthentication": false, |
|||
"anonymousAuthentication": true, |
|||
"iisExpress": { |
|||
"applicationUrl": "http://localhost:53596/", |
|||
"sslPort": 44300 |
|||
} |
|||
}, |
|||
"profiles": { |
|||
"IIS Express": { |
|||
"commandName": "IISExpress", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
} |
|||
}, |
|||
"Volo.Abp.AspNetCore": { |
|||
"commandName": "Project", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
}, |
|||
"applicationUrl": "https://localhost:5001;http://localhost:5000" |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,449 @@ |
|||
using System; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using ICSharpCode.SharpZipLib.Core; |
|||
using ICSharpCode.SharpZipLib.Zip; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Logging.Abstractions; |
|||
using Volo.Abp.Cli.Args; |
|||
using Volo.Abp.Cli.Commands.Services; |
|||
using Volo.Abp.Cli.ProjectBuilding; |
|||
using Volo.Abp.Cli.ProjectBuilding.Building; |
|||
using Volo.Abp.Cli.ProjectBuilding.Templates.App; |
|||
using Volo.Abp.Cli.ProjectBuilding.Templates.Microservice; |
|||
using Volo.Abp.Cli.Utils; |
|||
|
|||
namespace Volo.Abp.Cli.Commands |
|||
{ |
|||
public abstract class ProjectCreationCommandBase |
|||
{ |
|||
public ConnectionStringProvider ConnectionStringProvider { get; } |
|||
public ILogger<NewCommand> Logger { get; set; } |
|||
|
|||
public ProjectCreationCommandBase(ConnectionStringProvider connectionStringProvider) |
|||
{ |
|||
ConnectionStringProvider = connectionStringProvider; |
|||
|
|||
Logger = NullLogger<NewCommand>.Instance; |
|||
} |
|||
|
|||
protected ProjectBuildArgs GetProjectBuildArgs(CommandLineArgs commandLineArgs, string template, string projectName) |
|||
{ |
|||
var version = commandLineArgs.Options.GetOrNull(Options.Version.Short, Options.Version.Long); |
|||
|
|||
if (version != null) |
|||
{ |
|||
Logger.LogInformation("Version: " + version); |
|||
} |
|||
|
|||
var preview = commandLineArgs.Options.ContainsKey(Options.Preview.Long); |
|||
if (preview) |
|||
{ |
|||
Logger.LogInformation("Preview: yes"); |
|||
} |
|||
|
|||
var databaseProvider = GetDatabaseProvider(commandLineArgs); |
|||
if (databaseProvider != DatabaseProvider.NotSpecified) |
|||
{ |
|||
Logger.LogInformation("Database provider: " + databaseProvider); |
|||
} |
|||
|
|||
var connectionString = GetConnectionString(commandLineArgs); |
|||
if (connectionString != null) |
|||
{ |
|||
Logger.LogInformation("Connection string: " + connectionString); |
|||
} |
|||
|
|||
var databaseManagementSystem = GetDatabaseManagementSystem(commandLineArgs); |
|||
if (databaseManagementSystem != DatabaseManagementSystem.NotSpecified) |
|||
{ |
|||
Logger.LogInformation("DBMS: " + databaseManagementSystem); |
|||
} |
|||
|
|||
var uiFramework = GetUiFramework(commandLineArgs); |
|||
if (uiFramework != UiFramework.NotSpecified) |
|||
{ |
|||
Logger.LogInformation("UI Framework: " + uiFramework); |
|||
} |
|||
|
|||
var publicWebSite = uiFramework != UiFramework.None && commandLineArgs.Options.ContainsKey(Options.PublicWebSite.Long); |
|||
if (publicWebSite) |
|||
{ |
|||
Logger.LogInformation("Public Web Site: yes"); |
|||
} |
|||
|
|||
var mobileApp = GetMobilePreference(commandLineArgs); |
|||
if (mobileApp != MobileApp.None) |
|||
{ |
|||
Logger.LogInformation("Mobile App: " + mobileApp); |
|||
} |
|||
|
|||
var gitHubAbpLocalRepositoryPath = commandLineArgs.Options.GetOrNull(Options.GitHubAbpLocalRepositoryPath.Long); |
|||
if (gitHubAbpLocalRepositoryPath != null) |
|||
{ |
|||
Logger.LogInformation("GitHub Abp Local Repository Path: " + gitHubAbpLocalRepositoryPath); |
|||
} |
|||
|
|||
var gitHubVoloLocalRepositoryPath = commandLineArgs.Options.GetOrNull(Options.GitHubVoloLocalRepositoryPath.Long); |
|||
if (gitHubVoloLocalRepositoryPath != null) |
|||
{ |
|||
Logger.LogInformation("GitHub Volo Local Repository Path: " + gitHubVoloLocalRepositoryPath); |
|||
} |
|||
|
|||
var templateSource = commandLineArgs.Options.GetOrNull(Options.TemplateSource.Short, Options.TemplateSource.Long); |
|||
if (templateSource != null) |
|||
{ |
|||
Logger.LogInformation("Template Source: " + templateSource); |
|||
} |
|||
|
|||
var createSolutionFolder = GetCreateSolutionFolderPreference(commandLineArgs); |
|||
|
|||
var outputFolder = commandLineArgs.Options.GetOrNull(Options.OutputFolder.Short, Options.OutputFolder.Long); |
|||
|
|||
var outputFolderRoot = |
|||
outputFolder != null ? Path.GetFullPath(outputFolder) : Directory.GetCurrentDirectory(); |
|||
|
|||
SolutionName solutionName; |
|||
if (MicroserviceServiceTemplateBase.IsMicroserviceServiceTemplate(template)) |
|||
{ |
|||
var microserviceSolutionName = FindMicroserviceSolutionName(outputFolderRoot); |
|||
|
|||
if (microserviceSolutionName == null) |
|||
{ |
|||
throw new CliUsageException("This command should be run inside a folder that contains a microservice solution!"); |
|||
} |
|||
|
|||
solutionName = SolutionName.Parse(microserviceSolutionName, projectName); |
|||
outputFolder = MicroserviceServiceTemplateBase.CalculateTargetFolder(outputFolderRoot, projectName); |
|||
uiFramework = uiFramework == UiFramework.NotSpecified ? FindMicroserviceSolutionUiFramework(outputFolderRoot) : uiFramework; |
|||
} |
|||
else |
|||
{ |
|||
solutionName = SolutionName.Parse(projectName); |
|||
|
|||
outputFolder = createSolutionFolder ? |
|||
Path.Combine(outputFolderRoot, SolutionName.Parse(projectName).FullName) : |
|||
outputFolderRoot; |
|||
} |
|||
|
|||
IO.DirectoryHelper.CreateIfNotExists(outputFolder); |
|||
|
|||
Logger.LogInformation("Output folder: " + outputFolder); |
|||
|
|||
if (connectionString == null && |
|||
databaseManagementSystem != DatabaseManagementSystem.NotSpecified && |
|||
databaseManagementSystem != DatabaseManagementSystem.SQLServer) |
|||
{ |
|||
connectionString = ConnectionStringProvider.GetByDbms(databaseManagementSystem, outputFolder); |
|||
} |
|||
|
|||
commandLineArgs.Options.Add(CliConsts.Command, commandLineArgs.Command); |
|||
|
|||
return new ProjectBuildArgs( |
|||
solutionName, |
|||
template, |
|||
version, |
|||
outputFolder, |
|||
databaseProvider, |
|||
databaseManagementSystem, |
|||
uiFramework, |
|||
mobileApp, |
|||
publicWebSite, |
|||
gitHubAbpLocalRepositoryPath, |
|||
gitHubVoloLocalRepositoryPath, |
|||
templateSource, |
|||
commandLineArgs.Options, |
|||
connectionString |
|||
); |
|||
} |
|||
|
|||
protected void ExtractProjectZip(ProjectBuildResult project, string outputFolder) |
|||
{ |
|||
using (var templateFileStream = new MemoryStream(project.ZipContent)) |
|||
{ |
|||
using (var zipInputStream = new ZipInputStream(templateFileStream)) |
|||
{ |
|||
var zipEntry = zipInputStream.GetNextEntry(); |
|||
while (zipEntry != null) |
|||
{ |
|||
if (string.IsNullOrWhiteSpace(zipEntry.Name)) |
|||
{ |
|||
zipEntry = zipInputStream.GetNextEntry(); |
|||
continue; |
|||
} |
|||
|
|||
var fullZipToPath = Path.Combine(outputFolder, zipEntry.Name); |
|||
var directoryName = Path.GetDirectoryName(fullZipToPath); |
|||
|
|||
if (!string.IsNullOrEmpty(directoryName)) |
|||
{ |
|||
Directory.CreateDirectory(directoryName); |
|||
} |
|||
|
|||
var fileName = Path.GetFileName(fullZipToPath); |
|||
if (fileName.Length == 0) |
|||
{ |
|||
zipEntry = zipInputStream.GetNextEntry(); |
|||
continue; |
|||
} |
|||
|
|||
var buffer = new byte[4096]; // 4K is optimum
|
|||
using (var streamWriter = File.Create(fullZipToPath)) |
|||
{ |
|||
StreamUtils.Copy(zipInputStream, streamWriter, buffer); |
|||
} |
|||
|
|||
zipEntry = zipInputStream.GetNextEntry(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
protected void OpenRelatedWebPage(ProjectBuildArgs projectArgs, |
|||
string template, |
|||
bool isTiered, |
|||
CommandLineArgs commandLineArgs) |
|||
{ |
|||
if (AppTemplateBase.IsAppTemplate(template)) |
|||
{ |
|||
var isCommercial = template == AppProTemplate.TemplateName; |
|||
OpenThanksPage(projectArgs.UiFramework, projectArgs.DatabaseProvider, isTiered || commandLineArgs.Options.ContainsKey("separate-identity-server"), isCommercial); |
|||
} |
|||
else if (MicroserviceTemplateBase.IsMicroserviceTemplate(template)) |
|||
{ |
|||
OpenMicroserviceDocumentPage(); |
|||
} |
|||
} |
|||
|
|||
protected string FindMicroserviceSolutionName(string outputFolderRoot) |
|||
{ |
|||
var slnFile = Directory.GetFiles(outputFolderRoot, "*.sln").FirstOrDefault(); |
|||
|
|||
if (slnFile == null) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
return Path.GetFileName(slnFile).RemovePostFix(".sln"); |
|||
} |
|||
|
|||
protected UiFramework FindMicroserviceSolutionUiFramework(string outputFolderRoot) |
|||
{ |
|||
if (Directory.Exists(Path.Combine(outputFolderRoot, "apps", "blazor"))) |
|||
{ |
|||
return UiFramework.Blazor; |
|||
} |
|||
if (Directory.Exists(Path.Combine(outputFolderRoot, "apps", "web"))) |
|||
{ |
|||
return UiFramework.Mvc; |
|||
} |
|||
if (Directory.Exists(Path.Combine(outputFolderRoot, "apps", "angular"))) |
|||
{ |
|||
return UiFramework.Angular; |
|||
} |
|||
|
|||
return UiFramework.None; |
|||
} |
|||
|
|||
protected void OpenThanksPage(UiFramework uiFramework, DatabaseProvider databaseProvider, bool tiered, bool commercial) |
|||
{ |
|||
uiFramework = uiFramework == UiFramework.NotSpecified || uiFramework == UiFramework.None ? UiFramework.Mvc : uiFramework; |
|||
databaseProvider = databaseProvider == DatabaseProvider.NotSpecified ? DatabaseProvider.EntityFrameworkCore : databaseProvider; |
|||
|
|||
var urlPrefix = commercial ? "commercial" : "www"; |
|||
var tieredYesNo = tiered ? "yes" : "no"; |
|||
var url = $"https://{urlPrefix}.abp.io/project-created-success?ui={uiFramework:g}&db={databaseProvider:g}&tiered={tieredYesNo}"; |
|||
|
|||
CmdHelper.OpenWebPage(url); |
|||
} |
|||
|
|||
protected void OpenMicroserviceDocumentPage() |
|||
{ |
|||
var url = "https://docs.abp.io/en/commercial/latest/startup-templates/microservice/index"; |
|||
|
|||
CmdHelper.OpenWebPage(url); |
|||
} |
|||
|
|||
protected bool GetCreateSolutionFolderPreference(CommandLineArgs commandLineArgs) |
|||
{ |
|||
return commandLineArgs.Options.ContainsKey(Options.CreateSolutionFolder.Long) |
|||
|| commandLineArgs.Options.ContainsKey(Options.CreateSolutionFolder.Short); |
|||
} |
|||
|
|||
protected static string GetConnectionString(CommandLineArgs commandLineArgs) |
|||
{ |
|||
var connectionString = commandLineArgs.Options.GetOrNull(Options.ConnectionString.Short, Options.ConnectionString.Long); |
|||
return string.IsNullOrWhiteSpace(connectionString) ? null : connectionString; |
|||
} |
|||
|
|||
protected virtual DatabaseProvider GetDatabaseProvider(CommandLineArgs commandLineArgs) |
|||
{ |
|||
var optionValue = commandLineArgs.Options.GetOrNull(Options.DatabaseProvider.Short, Options.DatabaseProvider.Long); |
|||
switch (optionValue) |
|||
{ |
|||
case "ef": |
|||
return DatabaseProvider.EntityFrameworkCore; |
|||
case "mongodb": |
|||
return DatabaseProvider.MongoDb; |
|||
default: |
|||
return DatabaseProvider.NotSpecified; |
|||
} |
|||
} |
|||
|
|||
protected virtual DatabaseManagementSystem GetDatabaseManagementSystem(CommandLineArgs commandLineArgs) |
|||
{ |
|||
var optionValue = commandLineArgs.Options.GetOrNull(Options.DatabaseManagementSystem.Short, Options.DatabaseManagementSystem.Long); |
|||
|
|||
if (optionValue == null) |
|||
{ |
|||
return DatabaseManagementSystem.NotSpecified; |
|||
} |
|||
|
|||
switch (optionValue.ToLowerInvariant()) |
|||
{ |
|||
case "sqlserver": |
|||
return DatabaseManagementSystem.SQLServer; |
|||
case "mysql": |
|||
return DatabaseManagementSystem.MySQL; |
|||
case "postgresql": |
|||
return DatabaseManagementSystem.PostgreSQL; |
|||
case "oracle-devart": |
|||
return DatabaseManagementSystem.OracleDevart; |
|||
case "sqlite": |
|||
return DatabaseManagementSystem.SQLite; |
|||
case "oracle": |
|||
return DatabaseManagementSystem.Oracle; |
|||
default: |
|||
return DatabaseManagementSystem.NotSpecified; |
|||
} |
|||
} |
|||
|
|||
protected virtual MobileApp GetMobilePreference(CommandLineArgs commandLineArgs) |
|||
{ |
|||
var optionValue = commandLineArgs.Options.GetOrNull(Options.Mobile.Short, Options.Mobile.Long); |
|||
|
|||
switch (optionValue) |
|||
{ |
|||
case "none": |
|||
return MobileApp.None; |
|||
case "react-native": |
|||
return MobileApp.ReactNative; |
|||
default: |
|||
return MobileApp.None; |
|||
} |
|||
} |
|||
|
|||
protected virtual UiFramework GetUiFramework(CommandLineArgs commandLineArgs) |
|||
{ |
|||
if (commandLineArgs.Options.ContainsKey("no-ui")) |
|||
{ |
|||
return UiFramework.None; |
|||
} |
|||
|
|||
var optionValue = commandLineArgs.Options.GetOrNull(Options.UiFramework.Short, Options.UiFramework.Long); |
|||
switch (optionValue) |
|||
{ |
|||
case "none": |
|||
return UiFramework.None; |
|||
case "mvc": |
|||
return UiFramework.Mvc; |
|||
case "angular": |
|||
return UiFramework.Angular; |
|||
case "blazor": |
|||
return UiFramework.Blazor; |
|||
case "blazor-server": |
|||
return UiFramework.BlazorServer; |
|||
default: |
|||
return UiFramework.NotSpecified; |
|||
} |
|||
} |
|||
|
|||
public static class Options |
|||
{ |
|||
public static class Template |
|||
{ |
|||
public const string Short = "t"; |
|||
public const string Long = "template"; |
|||
} |
|||
|
|||
public static class DatabaseProvider |
|||
{ |
|||
public const string Short = "d"; |
|||
public const string Long = "database-provider"; |
|||
} |
|||
|
|||
public static class DatabaseManagementSystem |
|||
{ |
|||
public const string Short = "dbms"; |
|||
public const string Long = "database-management-system"; |
|||
} |
|||
|
|||
public static class OutputFolder |
|||
{ |
|||
public const string Short = "o"; |
|||
public const string Long = "output-folder"; |
|||
} |
|||
|
|||
public static class GitHubAbpLocalRepositoryPath |
|||
{ |
|||
public const string Long = "abp-path"; |
|||
} |
|||
|
|||
public static class GitHubVoloLocalRepositoryPath |
|||
{ |
|||
public const string Long = "volo-path"; |
|||
} |
|||
|
|||
public static class Version |
|||
{ |
|||
public const string Short = "v"; |
|||
public const string Long = "version"; |
|||
} |
|||
|
|||
public static class UiFramework |
|||
{ |
|||
public const string Short = "u"; |
|||
public const string Long = "ui"; |
|||
} |
|||
|
|||
public static class Mobile |
|||
{ |
|||
public const string Short = "m"; |
|||
public const string Long = "mobile"; |
|||
} |
|||
|
|||
public static class PublicWebSite |
|||
{ |
|||
public const string Long = "with-public-website"; |
|||
} |
|||
|
|||
public static class TemplateSource |
|||
{ |
|||
public const string Short = "ts"; |
|||
public const string Long = "template-source"; |
|||
} |
|||
|
|||
public static class ConnectionString |
|||
{ |
|||
public const string Short = "cs"; |
|||
public const string Long = "connection-string"; |
|||
} |
|||
|
|||
public static class CreateSolutionFolder |
|||
{ |
|||
public const string Short = "csf"; |
|||
public const string Long = "create-solution-folder"; |
|||
} |
|||
|
|||
public static class Tiered |
|||
{ |
|||
public const string Long = "tiered"; |
|||
} |
|||
|
|||
public static class Preview |
|||
{ |
|||
public const string Long = "preview"; |
|||
} |
|||
} |
|||
} |
|||
} |
|||