diff --git a/docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/POST.md b/docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/POST.md index a1c63529a2..c82365efee 100644 --- a/docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/POST.md +++ b/docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/POST.md @@ -12,7 +12,7 @@ The most common question is; The answer is, **you don't**. ABP Angular application is integrated with the backend (HttpApi.Host project) where it loads the configurations, **permissions** etc. For none-tiered angular applications, **HttpApi.Host** project also has IdentityServer4 embedded; also serving as **Authorization Server**. Angular application authentication flow is shown below. -auth-diagram +![auth-diagram](auth-diagram.jpeg) > What if I want Azure AD as my authorization server and not IdentityServer? @@ -80,7 +80,7 @@ Navigate to Manage Azure Active Directory in [azure portal](https://portal.azure Enter a name for your application and **App.SelfUrl** + **AzureAd.CallbackPath** as redirect uri then register. -azure-app-register +![azure-app-register](azure-app-register.JPG) Now navigate to **Authentication** on the left menu and enable **ID tokens**. diff --git a/docs/en/Tutorials/Part-10.md b/docs/en/Tutorials/Part-10.md index 5d5ea9618b..563de95d68 100644 --- a/docs/en/Tutorials/Part-10.md +++ b/docs/en/Tutorials/Part-10.md @@ -948,7 +948,7 @@ Since the HTTP APIs have been changed, you need to update Angular client side [s Run the following command in the `angular` folder (you may need to stop the angular application): ```bash -abp generate-proxy +abp generate-proxy -t ng ``` This command will update the service proxy files under the `/src/app/proxy/` folder. diff --git a/docs/en/Tutorials/Part-2.md b/docs/en/Tutorials/Part-2.md index b84ece9e97..4b29231164 100644 --- a/docs/en/Tutorials/Part-2.md +++ b/docs/en/Tutorials/Part-2.md @@ -10,7 +10,7 @@ In this tutorial series, you will build an ABP based web application named `Acme.BookStore`. This application is used to manage a list of books and their authors. It is developed using the following technologies: -* **{{DB_Value}}** as the ORM provider. +* **{{DB_Value}}** as the ORM provider. * **{{UI_Value}}** as the UI Framework. This tutorial is organized as the following parts; @@ -80,13 +80,13 @@ You can see the **book list** returned from the server. You can also check the * Let's **create a new book** using the `create` function: ````js -acme.bookStore.books.book.create({ - name: 'Foundation', - type: 7, - publishDate: '1951-05-24', - price: 21.5 - }).then(function (result) { - console.log('successfully created the book with id: ' + result.id); +acme.bookStore.books.book.create({ + name: 'Foundation', + type: 7, + publishDate: '1951-05-24', + price: 21.5 + }).then(function (result) { + console.log('successfully created the book with id: ' + result.id); }); ```` @@ -187,7 +187,7 @@ namespace Acme.BookStore.Web.Pages.Books { public void OnGet() { - + } } } @@ -461,7 +461,7 @@ For more information, see the [RoutesService document](../UI/Angular/Modifying-t Once the host application is running, execute the following command in the `angular` folder: ```bash -abp generate-proxy +abp generate-proxy -t ng ``` This command will create the following files under the `/src/app/proxy/books` folder: @@ -654,7 +654,7 @@ Open the `Books.razor` and replace the content as the following: > If you see some syntax errors, you can ignore them if your application properly built and run. Visual Studio still has some bugs with Blazor. * Inherited from the `AbpCrudPageBase` which implements all the CRUD details for us. -* `Entities`, `TotalCount`, `PageSize`, `OnDataGridReadAsync` are defined in the base blass. +* `Entities`, `TotalCount`, `PageSize`, `OnDataGridReadAsync` are defined in the base class. * Injected `IStringLocalizer` (as `L` object) and used for localization. While the code above pretty easy to understand, you can check the Blazorise [Card](https://blazorise.com/docs/components/card/) and [DataGrid](https://blazorise.com/docs/extensions/datagrid/) documents to understand them better. diff --git a/docs/en/Tutorials/Part-9.md b/docs/en/Tutorials/Part-9.md index d6fa004596..0e271a5e4e 100644 --- a/docs/en/Tutorials/Part-9.md +++ b/docs/en/Tutorials/Part-9.md @@ -605,7 +605,7 @@ function configureRoutes(routes: RoutesService) { Run the following command in the `angular` folder: ```bash -abp generate-proxy +abp generate-proxy -t ng ``` This command generates the service proxy for the author service and the related model (DTO) classes: diff --git a/docs/en/UI/Angular/Service-Proxies.md b/docs/en/UI/Angular/Service-Proxies.md index 18e5a3a0cc..dc8433f7de 100644 --- a/docs/en/UI/Angular/Service-Proxies.md +++ b/docs/en/UI/Angular/Service-Proxies.md @@ -15,7 +15,7 @@ ABP introduces an endpoint that exposes server-side method contracts. When the ` Run the following command in the **root folder** of the angular application: ```bash -abp generate-proxy +abp generate-proxy -t ng ``` The command without any parameters creates proxies only for your own application's services and places them in your default Angular application. There are several parameters you may use to modify this behavior. See the [CLI documentation](../../CLI) for details. diff --git a/docs/zh-Hans/Tutorials/Part-1.md b/docs/zh-Hans/Tutorials/Part-1.md index 3ff216fb76..927463e100 100644 --- a/docs/zh-Hans/Tutorials/Part-1.md +++ b/docs/zh-Hans/Tutorials/Part-1.md @@ -2,32 +2,15 @@ ````json //[doc-params] { - "UI": ["MVC","NG"], + "UI": ["MVC","Blazor","BlazorServer","NG"], "DB": ["EF","Mongo"] } ```` -{{ -if UI == "MVC" - UI_Text="mvc" -else if UI == "NG" - UI_Text="angular" -else - UI_Text="?" -end -if DB == "EF" - DB_Text="Entity Framework Core" -else if DB == "Mongo" - DB_Text="MongoDB" -else - DB_Text="?" -end -}} - ## 关于本教程 在本系列教程中, 你将构建一个名为 `Acme.BookStore` 的用于管理书籍及其作者列表的基于ABP的应用程序. 它是使用以下技术开发的: -* **{{DB_Text}}** 做为ORM提供程序. +* **{{DB_Text}}** 做为数据库提供程序. * **{{UI_Value}}** 做为UI框架. 本教程分为以下部分: @@ -40,16 +23,29 @@ end - [Part 6: 作者: 领域层](Part-6.md) - [Part 7: 作者: 数据库集成](Part-7.md) - [Part 8: 作者: 应用服务层](Part-8.md) -- [Part 9: 作者: 用户页面](Part-9.md) +- [Part 9: 作者: 用户界面](Part-9.md) - [Part 10: 图书到作者的关系](Part-10.md) ## 下载源码 -本教程根据你的**UI** 和 **Database**偏好有多个版,我们准备了两种可供下载的源码组合: +本教程根据你的**UI** 和 **数据库**偏好有多个版本,我们准备了几种可供下载的源码组合: * [MVC (Razor Pages) UI 与 EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) +* [Blazor UI 与 EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore) * [Angular UI 与 MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) +> 如果你在Windows中遇到 "文件名太长" or "解压错误", 很可能与Windows最大文件路径限制有关. Windows文件路径的最大长度为250字符. 为了解决这个问题,参阅 [在Windows 10中启用长路径](https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd#enable-long-paths-in-windows-10-version-1607-and-later). + +> 如果你遇到与Git相关的长路径错误, 尝试使用下面的命令在Windows中启用长路径. 参阅 https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path +> `git config --system core.longpaths true` + +{{if UI == "MVC" && DB == "EF"}} + +### 视频教程 + +本章也被录制为视频教程 **发布在YouTube**. + +{{end}} ## 创建解决方案 在开始开发之前,请按照[入门教程](../Getting-Started.md)创建名为 `Acme.BookStore` 的新解决方案. @@ -58,12 +54,12 @@ end 启动模板中的**领域层**分为两个项目: - - `Acme.BookStore.Domain`包含你的[实体](https://docs.abp.io/zh-Hans/abp/latest/Entities), [领域服务](https://docs.abp.io/zh-Hans/abp/latest/Domain-Services)和其他核心域对象. + - `Acme.BookStore.Domain`包含你的[实体](../Entities.md), [领域服务](../Domain-Services.md)和其他核心域对象. - `Acme.BookStore.Domain.Shared`包含可与客户共享的常量,枚举或其他域相关对象. -在解决方案的**领域层**(`Acme.BookStore.Domain`项目)中定义你的实体. +在解决方案的**领域层**(`Acme.BookStore.Domain`项目)中定义你的实体. -该应用程序的主要实体是`Book`. 在`Acme.BookStore.Domain`项目中创建一个 `Books` 文件夹并在其中添加一个名为 `Book` 的类,如下所示: +该应用程序的主要实体是`Book`. 在`Acme.BookStore.Domain`项目中创建一个 `Books` 文件夹(命名空间),并在其中添加名为 `Book` 的类,如下所示: ````csharp using System; @@ -84,15 +80,15 @@ namespace Acme.BookStore.Books } ```` -* ABP为实体提供了两个基本的基类: `AggregateRoot`和`Entity`. **Aggregate Root**是[**领域驱动设计**](./Domain-Driven-Design.md) 概念之一. 可以视为直接查询和处理的根实体(请参阅[实体文档](../Entities.md)). -* `Book`实体继承了`AuditedAggregateRoot`,`AuditedAggregateRoot`类在`AggregateRoot`类的基础上添加了一些审计属性(`CreationTime`, `CreatorId`, `LastModificationTime` 等). ABP框架自动为你管理这些属性. -* `Guid`是`Book`实体的主键类型. +* ABP为实体提供了两个基本的基类: `AggregateRoot`和`Entity`. **Aggregate Root**是[**领域驱动设计**](../Domain-Driven-Design.md) 概念之一. 可以视为直接查询和处理的根实体(请参阅[实体文档](../Entities.md)). +* `Book`实体继承了`AuditedAggregateRoot`,`AuditedAggregateRoot`类在`AggregateRoot`类的基础上添加了一些基础[审计](../Audit-Logging.md)属性(例如`CreationTime`, `CreatorId`, `LastModificationTime` 等). ABP框架自动为你管理这些属性. +* `Guid`是`Book`实体的**主键类型**. > 为了保持简单,本教程将实体属性保留为 **public get/set** . 如果你想了解关于DDD最佳实践,请参阅[实体文档](../Entities.md). ### BookType枚举 -上面所用到了 `BookType` 枚举,在 `Acme.BookStore.Domain.Shared` 项目创建 `BookType`. +`Book`实体使用了`BookType`枚举. 在`Acme.BookStore.Domain.Shared`项目中创建`Books`文件夹(命名空间),并在其中添加`BookType`: ````csharp namespace Acme.BookStore.Books @@ -150,28 +146,37 @@ public class BookStoreMongoDbContext : AbpMongoDbContext ### 将Book实体映射到数据库表 -在 `Acme.BookStore.EntityFrameworkCore` 项目中打开 `BookStoreDbContextModelCreatingExtensions.cs` 文件,添加 `Book` 实体的映射代码. 最终类应为: + +打开`BookStoreDbContext`类的`OnModelCreating`方法,为`Book`实体添加映射代码: ````csharp using Acme.BookStore.Books; -using Microsoft.EntityFrameworkCore; -using Volo.Abp; -using Volo.Abp.EntityFrameworkCore.Modeling; +... namespace Acme.BookStore.EntityFrameworkCore { - public static class BookStoreDbContextModelCreatingExtensions + public class BookStoreDbContext : + AbpDbContext, + IIdentityDbContext, + ITenantManagementDbContext { - public static void ConfigureBookStore(this ModelBuilder builder) + ... + + protected override void OnModelCreating(ModelBuilder builder) { - Check.NotNull(builder, nameof(builder)); + base.OnModelCreating(builder); + + /* Include modules to your migration db context */ + + builder.ConfigurePermissionManagement(); + ... /* Configure your own tables/entities inside here */ builder.Entity(b => { b.ToTable(BookStoreConsts.DbTablePrefix + "Books", - BookStoreConsts.DbSchema); + BookStoreConsts.DbSchema); b.ConfigureByConvention(); //auto configure for the base class props b.Property(x => x.Name).IsRequired().HasMaxLength(128); }); @@ -180,14 +185,14 @@ namespace Acme.BookStore.EntityFrameworkCore } ```` -* `BookStoreConsts` 含有用于表的架构和表前缀的常量值. 你不必使用它,但建议在单点控制表前缀. -* `ConfigureByConvention()` 方法优雅的配置/映射继承的属性,应始终对你所有的实体使用它. +* `BookStoreConsts` 含有用于表的架构和表前缀的常量值. 使用它不是强制的,但建议在统一的地方控制表前缀. +* `ConfigureByConvention()` 方法优雅的配置/映射继承的属性,应对所有的实体使用它. ### 添加数据迁移 -启动模板使用[EF Core Code First Migrations](https://docs.microsoft.com/zh-cn/ef/core/managing-schemas/migrations/)创建和维护数据库架构. 我们应该创建一个新的迁移并且应用到数据库. +本示例使用[EF Core Code First Migrations](https://docs.microsoft.com/zh-cn/ef/core/managing-schemas/migrations/).因为我们修改了数据库映射配置,我们必须创建一个新的迁移并且应用到数据库. -在 `Acme.BookStore.EntityFrameworkCore.DbMigrations` 目录打开命令行终端输入以下命令: +在 `Acme.BookStore.EntityFrameworkCore` 目录打开命令行终端输入以下命令: ```bash dotnet ef migrations add Created_Book_Entity @@ -205,7 +210,7 @@ dotnet ef migrations add Created_Book_Entity > >在运行应用程序之前最好将初始数据添加到数据库中. 本节介绍ABP框架的[数据种子系统](../Data-Seeding.md). 如果你不想创建种子数据可以跳过本节,但是建议你遵循它来学习这个有用的ABP Framework功能。 -在 `*.Domain` 项目下创建派生 `IDataSeedContributor` 的类,并且拷贝以下代码: +在 `*.Domain` 项目下创建 `IDataSeedContributor` 的派生类,并且拷贝以下代码: ```csharp using System; @@ -258,7 +263,7 @@ namespace Acme.BookStore } ``` -* 如果数据库中当前没有图书,则此代码使用 `IRepository`(默认为[repository](../Repositories.md))将两本书插入数据库. +* 如果数据库中当前没有图书,则此代码使用 `IRepository`(默认[repository](../Repositories.md))将两本书插入数据库. ### 更新数据库 @@ -279,7 +284,7 @@ namespace Acme.BookStore ### BookDto -`CrudAppService` 基类需要定义实体的基本DTO. 在 `Acme.BookStore.Application.Contracts` 项目中创建一个名为 `BookDto` 的DTO类: +`CrudAppService` 基类需要定义实体的基本DTO. 在 `Acme.BookStore.Application.Contracts` 项目中创建 `Books` 文件夹(命名空间), 并在其中添加名为 `BookDto` 的DTO类: ````C# using System; @@ -300,9 +305,9 @@ namespace Acme.BookStore } ```` -* **DTO**类被用来在 **表示层** 和 **应用层** **传递数据**.查看[DTO文档](https://docs.abp.io/zh-Hans/abp/latest/Data-Transfer-Objects)查看更多信息. -* 为了在页面上展示书籍信息,`BookDto`被用来将书籍数据传递到表示层. -* `BookDto`继承自 `AuditedEntityDto`.跟上面定义的 `Book` 实体一样具有一些审计属性. +* **DTO**类被用来在 **表示层** 和 **应用层** **传递数据**.参阅[DTO文档](https://docs.abp.io/zh-Hans/abp/latest/Data-Transfer-Objects). +* 为了在用户界面上展示书籍信息,`BookDto`被用来将书籍数据传递到表示层. +* `BookDto`继承自 `AuditedEntityDto`.与上面定义的 `Book` 实体一样具有一些审计属性. 在将书籍返回到表示层时,需要将`Book`实体转换为`BookDto`对象. [AutoMapper](https://automapper.org)库可以在定义了正确的映射时自动执行此转换. 启动模板配置了AutoMapper,因此你只需在`Acme.BookStore.Application`项目的`BookStoreApplicationAutoMapperProfile`类中定义映射: @@ -322,12 +327,11 @@ namespace Acme.BookStore } ```` -> 参阅 [对象对对象映射](../Object-To-Object-Mapping.md) 文档了解详情. +> 参阅 [对象到对象映射](../Object-To-Object-Mapping.md) 文档了解详情. ### CreateUpdateBookDto -在`Acme.BookStore.Application.Contracts`项目中创建一个名为 `CreateUpdateBookDto` 的DTO类: - +在`Acme.BookStore.Application.Contracts`项目中创建 `Books` 文件夹(命名空间),并在其中添加名为 `CreateUpdateBookDto` 的DTO类: ````csharp using System; using System.ComponentModel.DataAnnotations; @@ -354,7 +358,7 @@ namespace Acme.BookStore.Books ```` * 这个DTO类被用于在创建或更新书籍的时候从用户界面获取图书信息. -* 它定义了数据注释属性(如`[Required]`)来定义属性的验证. DTO由ABP框架[自动验证](https://docs.abp.io/zh-Hans/abp/latest/Validation). +* 它定义了数据注释特性(如`[Required]`)来定义属性的验证规则. DTO由ABP框架[自动验证](https://docs.abp.io/zh-Hans/abp/latest/Validation). 就像上面的`BookDto`一样,创建一个从`CreateUpdateBookDto`对象到`Book`实体的映射,最终映射配置类如下: @@ -378,7 +382,7 @@ namespace Acme.BookStore ### IBookAppService -下一步是为应用程序定义接口,在`Acme.BookStore.Application.Contracts`项目中定义一个名为`IBookAppService`的接口: +下一步是为应用程序定义接口,在`Acme.BookStore.Application.Contracts`项目创建 `Books` 文件夹(命名空间),并在其中添加名为`IBookAppService`的接口: ````csharp using System; @@ -400,12 +404,12 @@ namespace Acme.BookStore.Books ```` * 框架定义应用程序服务的接口**不是必需的**. 但是,它被建议作为最佳实践. -* `ICrudAppService`定义了常见的**CRUD**方法:`GetAsync`,`GetListAsync`,`CreateAsync`,`UpdateAsync`和`DeleteAsync`. 你可以从空的`IApplicationService`接口继承并手动定义自己的方法(将在下一部分中完成). -* `ICrudAppService`有一些变体, 你可以在每个方法中使用单独的DTO,也可以分别单独指定(例如使用不同的DTO进行创建和更新). +* `ICrudAppService`定义了常见的**CRUD**方法:`GetAsync`,`GetListAsync`,`CreateAsync`,`UpdateAsync`和`DeleteAsync`. 从这个接口扩展不是必需的,你可以从空的`IApplicationService`接口继承并手动定义自己的方法(将在下一部分中完成). +* `ICrudAppService`有一些变体, 你可以在每个方法中使用单独的DTO(例如使用不同的DTO进行创建和更新). ### BookAppService -在`Acme.BookStore.Application`项目中创建名为 `BookAppService` 的 `IBookAppService` 实现: +是时候实现`IBookAppService`接口了.在`Acme.BookStore.Application`项目中创建 `Books` 文件夹(命名空间),并在其中添加名为 `BookAppService` 的类: ````csharp using System; @@ -439,19 +443,20 @@ namespace Acme.BookStore.Books ### 自动生成API Controllers -你通常创建**Controller**以将应用程序服务公开为**HTTP API**端点. 因此允许浏览器或第三方客户端通过AJAX调用它们. +在典型的ASP.NET Core应用程序中,你创建**API Controller**以将应用程序服务公开为**HTTP API**端点. 这将允许浏览器或第三方客户端通过HTTP调用它们. -ABP可以[**自动**](../API/Auto-API-Controllers.md)按照惯例将你的应用程序服务配置为MVC API控制器. +ABP可以[**自动**](../API/Auto-API-Controllers.md)按照约定将你的应用程序服务配置为MVC API控制器. ### Swagger UI 启动模板配置为使用[Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore)运行[swagger UI](https://swagger.io/tools/swagger-ui/). 运行应用程序并在浏览器中输入`https://localhost:XXXX/swagger/`(用你自己的端口替换XXXX)作为URL. +使用`CTRL+F5`运行应用程序 ({{if UI=="MVC"}}`Acme.BookStore.Web`{{else}}`Acme.BookStore.HttpApi.Host`{{end}})并使用浏览器访问`https://localhost:/swagger/` on your browser. 使用你自己的端口号替换 ``. -你会看到一些内置的接口和`Book`的接口,它们都是REST风格的: +你会看到一些内置的服务端点和`Book`服务,它们都是REST风格的端点: -![bookstore-swagger](images/bookstore-swagger.png) +![bookstore-swagger](./images/bookstore-swagger.png) -Swagger有一个很好的UI来测试API. +Swagger有一个很好的UI来测试API. 你可以尝试执行`[GET] /api/app/book` API来获取书籍列表, 服务端会返回以下JSON结果: diff --git a/docs/zh-Hans/Tutorials/Part-2.md b/docs/zh-Hans/Tutorials/Part-2.md index decddfebff..fe0dd84c45 100644 --- a/docs/zh-Hans/Tutorials/Part-2.md +++ b/docs/zh-Hans/Tutorials/Part-2.md @@ -2,26 +2,10 @@ ````json //[doc-params] { - "UI": ["MVC","NG"], + "UI": ["MVC","Blazor","BlazorServer","NG"], "DB": ["EF","Mongo"] } ```` - -{{ -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 -}} - ## 关于本教程 在本系列教程中, 你将构建一个名为 `Acme.BookStore` 的用于管理书籍及其作者列表的基于ABP的应用程序. 它是使用以下技术开发的: @@ -44,16 +28,30 @@ end ## 下载源码 -本教程根据你的**UI** 和 **Database**偏好有多个版,我们准备了两种可供下载的源码组合: +本教程根据你的**UI** 和 **数据库**偏好有多个版本,我们准备了几种可供下载的源码组合: * [MVC (Razor Pages) UI 与 EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) +* [Blazor UI 与 EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore) * [Angular UI 与 MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) +> 如果你在Windows中遇到 "文件名太长" or "解压错误", 很可能与Windows最大文件路径限制有关. Windows文件路径的最大长度为250字符. 为了解决这个问题,参阅 [在Windows 10中启用长路径](https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd#enable-long-paths-in-windows-10-version-1607-and-later). + +> 如果你遇到与Git相关的长路径错误, 尝试使用下面的命令在Windows中启用长路径. 参阅 https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path +> `git config --system core.longpaths true` + +{{if UI == "MVC" && DB == "EF"}} + +### 视频教程 + +本章也被录制为视频教程 **发布在YouTube**. + +{{end}} + {{if UI == "MVC"}} ## 动态JavaScript代理 -通常在 **JavaScript** 端通过AJAX调用HTTP API端点. 你可以使用 `$.ajax` 或其他工具来调用端点. 但是ABP提供了更好的方法. +在 **JavaScript** 端通过AJAX调用HTTP API端点是常见的做法. 你可以使用 `$.ajax` 或其他工具来调用端点. 但是ABP提供了更好的方法. ABP**动态**为所有API端点创建 **[JavaScript代理](../UI/AspNetCore/Dynamic-JavaScript-Proxies.md)**. 所以你可以像调用**Javascript本地方法**一样使用任何**端点**. @@ -79,21 +77,21 @@ acme.bookStore.books.book.getList({}).done(function (result) { console.log(resul ![bookstore-getlist-result-network](images/bookstore-getlist-result-network.png) -Let's **create a new book** using the `create` function: - 让我们使用 `create` 函数**创建一本书**: ````js -acme.bookStore.books.book.create({ - name: 'Foundation', - type: 7, - publishDate: '1951-05-24', - price: 21.5 - }).then(function (result) { - console.log('successfully created the book with id: ' + result.id); +acme.bookStore.books.book.create({ + name: 'Foundation', + type: 7, + publishDate: '1951-05-24', + price: 21.5 + }).then(function (result) { + console.log('successfully created the book with id: ' + result.id); }); ```` +> 如果你下载了本教程的源代码并按照示例中的步骤操作,你需要传递`authorId`参数给创建方法以**创建一本新书**. + 您应该在控制台中看到类似以下的消息: ````text @@ -102,7 +100,7 @@ successfully created the book with id: 439b0ea8-923e-8e1e-5d97-39f2c7ac4246 检查数据库中的 `Books` 表你会看到新的一行. 你可以自己尝试使用 `get`, `update` 和 `delete` 函数. -我们将利用这些动态代理功能在接下来的章节来与服务器通信. +在接下来的章节,我们将利用这些动态代理函数与服务器通信. {{end}} @@ -112,7 +110,7 @@ successfully created the book with id: 439b0ea8-923e-8e1e-5d97-39f2c7ac4246 本地化文本位于 `Acme.BookStore.Domain.Shared` 项目的 `Localization/BookStore` 文件夹下: -![bookstore-localization-files](./images/bookstore-localization-files-v2.png) +![bookstore-localization-files](images/bookstore-localization-files-v2.png) 打开 `en.json` (*英文翻译*)文件并更改内容,如下所示: @@ -154,7 +152,7 @@ successfully created the book with id: 439b0ea8-923e-8e1e-5d97-39f2c7ac4246 * 为按钮项添加 `Menu:` 前缀. * 使用 `Enum::` 命名约定来本地化枚举成员. 当您这样做时ABP可以在某些适当的情况下自动将枚举本地化. -如果未在本地化文件中定义文本,则文本将**回退**到本地化键(作为ASP.NET Core的标准行为). +如果未在本地化文件中定义文本,则文本将**回退**到本地化键(ASP.NET Core的标准行为). > ABP本地化系统建立在[ASP.NET Core标准本地化](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization)系统之上,并以多种方式进行了扩展. 有关详细信息请参见[本地化文档](../Localization.md). @@ -162,7 +160,7 @@ successfully created the book with id: 439b0ea8-923e-8e1e-5d97-39f2c7ac4246 ## 创建图书页面 -是时候创建可见的和可用的东西了! 代替经典的MVC,我们将使用微软推荐的[Razor Pages UI](https://docs.microsoft.com/zh-cn/aspnet/core/tutorials/razor-pages/razor-pages-start). +是时候创建可见的和可用的东西了! 我们将使用微软推荐的[Razor Pages UI](https://docs.microsoft.com/zh-cn/aspnet/core/tutorials/razor-pages/razor-pages-start),而不是经典的MVC. 在 `Acme.BookStore.Web` 项目的 `Pages` 文件夹下创建一个名为新的 `Books` 的文件夹. 然后在文件夹右键选择 **添加 > Razor Page** 菜单. 输入名称 `Index`: @@ -189,13 +187,13 @@ namespace Acme.BookStore.Web.Pages.Books { public void OnGet() { - + } } } ``` -### 将Book页面添加到主菜单 +### 将图书页面添加到主菜单 打开 `Menus` 文件夹中的 `BookStoreMenuContributor` 类,在 `ConfigureMainMenuAsync` 方法的底部添加如下代码: @@ -223,9 +221,7 @@ context.Menu.AddItem( ### 图书列表 -We will use the [Datatables.net](https://datatables.net/) jQuery library to show the book list. Datatables library completely work via AJAX, it is fast, popular and provides a good user experience. - -我们将使用[Datatables.net](https://datatables.net/)JQuery插件来显示页面上的表格列表. [Datatables](https://datatables.net/)可以完全通过AJAX工作,速度快,并提供良好的用户体验. +我们将使用[Datatables.net](https://datatables.net/)JQuery插件来显示图书列表. [Datatables](https://datatables.net/)可以完全通过AJAX工作,速度快,并提供良好的用户体验. > Datatables插件在启动模板中配置,因此你可以直接在任何页面中使用它,无需在页面中引用样式和脚本文件. @@ -261,7 +257,7 @@ We will use the [Datatables.net](https://datatables.net/) jQuery library to show 在 `Pages/Books/` 文件夹中创建 `index.js`文件 -![bookstore-index-js-file](./images/bookstore-index-js-file-v3.png) +![bookstore-index-js-file](images/bookstore-index-js-file-v3.png) `index.js` 的内容如下: @@ -321,8 +317,8 @@ $(function () { ```` * `abp.localization.getResource` 获取一个函数,该函数用于使用服务器端定义的相同JSON文件对文本进行本地化. 通过这种方式你可以与客户端共享本地化值. -* `abp.libs.datatables.normalizeConfiguration`是另一个辅助方法.不是必须的, 但是它通过为缺少的选项提供常规值来简化数据表配置. -* `abp.libs.datatables.createAjax`是帮助ABP的动态JavaScript API代理跟[Datatable](https://datatables.net/)的格式相适应的辅助方法. +* `abp.libs.datatables.normalizeConfiguration`是一个辅助方法.不是必须的, 但是它通过为缺省的选项提供约定的值来简化[Datatables](https://datatables.net/)配置. +* `abp.libs.datatables.createAjax`是另一个辅助方法,用来适配ABP的动态JavaScript API代理和[Datatable](https://datatables.net/)期望的参数格式. * `acme.bookStore.books.book.getList` 是动态JavaScript代理函数(上面已经介绍过了) * [luxon](https://moment.github.io/luxon/) 库也是该解决方案中预先配置的标准库,你可以轻松地执行日期/时间操作. @@ -334,15 +330,13 @@ $(function () { ![Book list](images/bookstore-book-list-3.png) -这是一个完全正常工作的服务端分页,排序和本地化的图书列表. - -{{end}} +这是一个可以正常工作的,服务端分页,排序和本地化的图书列表. {{if UI == "NG"}} ## 安装NPM包 ->注意: 本教程基于ABP Framework v3.0.3+. 如果你的项目版本较旧,请升级您的解决方案. 如果要升级现有的v2.x项目,请参阅[迁移指南](../UI/Angular/Migration-Guide-v3.md). +>注意: 本教程基于ABP Framework v3.1.0+. 如果你的项目版本较旧,请升级您的解决方案. 如果要升级现有的v2.x项目,请参阅[迁移指南](../UI/Angular/Migration-Guide-v3.md). 在 `angular` 目录下打开命令行窗口,选择 `yarn` 命令安装NPM包: @@ -357,9 +351,7 @@ yarn - [Ng Bootstrap](https://ng-bootstrap.github.io/#/home) 用做UI组件库. - [ngx-datatable](https://swimlane.gitbook.io/ngx-datatable/) 用做 datatable 类库. -### BookModule - -运行以下命令创建一个名为 `BookModule` 的新模块: +运行以下命令在angular应用程序根目录创建一个名为 `BookModule` 的新模块: ```bash yarn ng generate module book --module app --routing --route books @@ -417,7 +409,7 @@ const routes: Routes = [ ]; ```` -现在打开 `src/app/route.provider.ts` 以下替换 `configureRoutes` 函数: +现在打开 `src/app/route.provider.ts` 替换 `configureRoutes` 函数为以下代码: ```js function configureRoutes(routes: RoutesService) { @@ -450,27 +442,31 @@ function configureRoutes(routes: RoutesService) { `RoutesService` 是ABP框架提供的用于配置主菜单和路由的服务. -* `path` 路由的URL. +* `path` 路由的URL. * `name` 菜单项的名称(参阅[本地化文档](../UI/Angular/Localization.md)了解更多). -* `iconClass` 菜单项的图标(你可以使用默认的[Font Awesome](https://fontawesome.com/)图标). -* `order` 菜单项的排序.我们定义了101,它显示在 "Administration" 项的后面. -* `layout` BooksModule路由的布局. 可以定义 `eLayoutType.application`, `eLayoutType.account` 或 `eLayoutType.empty`. +* `iconClass` 菜单项的图标(你可以使用默认的[Font Awesome](https://fontawesome.com/)图标). +* `order` 菜单项的排序. +* `layout` BooksModule路由的布局. (有三个预定义的布局类型: `eLayoutType.application`, `eLayoutType.account` 或 `eLayoutType.empty`). + +更多信息请参阅[RoutesService 文档](../UI/Angular/Modifying-the-Menu.md#via-routesservice). -更多信息请参阅[RoutesService 文档](https://docs.abp.io/en/abp/latest/UI/Angular/Modifying-the-Menu.md#via-routesservice). +### 生成服务代理 -### 生成代理 +[ABP CLI](../CLI.md) 提供 `generate-proxy` 命令为HTTP APIs生成客户端代理.有了这些代理,在客户端使用HTTP APIs变得更加方便. 运行 `generate-proxy` 命令前, 你的 host 必须正在运行. -ABP CLI提供了 `generate-proxy` 命令为你的服务HTTP API生成客户端代理简化客户端使用服务的成本. 运行 `generate-proxy` 命令前你的host必须正在运行. 参阅 [CLI 文档](../CLI.md). +> **警告**: 使用IIS Express时有一个问题; 它不允许从另一个进程连接应用程序. 如果你使用Visual Studio, 在运行按钮的下拉框中选择`Acme.BookStore.HttpApi.Host`,不要选择IIS Express, 如下图: -在 `angular` 文件夹下运行以下命令: +![vs-run-without-iisexpress](images/vs-run-without-iisexpress.png) + +启动host应用程序后,在 `angular` 文件夹下运行以下命令: ```bash -abp generate-proxy +abp generate-proxy -t ng ``` -生成的文件如下: +这个命令将在`/src/app/proxy/books`文件夹下产生以下文件: -![Generated files](./images/generated-proxies-2.png) +![Generated files](images/generated-proxies-3.png) ### BookComponent @@ -479,8 +475,7 @@ abp generate-proxy ```js import { ListService, PagedResultDto } from '@abp/ng.core'; import { Component, OnInit } from '@angular/core'; -import { BookDto } from './models'; -import { BookService } from './services'; +import { BookService, BookDto } from '@proxy/books'; @Component({ selector: 'app-book', @@ -494,7 +489,7 @@ export class BookComponent implements OnInit { constructor(public readonly list: ListService, private bookService: BookService) {} ngOnInit() { - const bookStreamCreator = (query) => this.bookService.getListByInput(query); + const bookStreamCreator = (query) => this.bookService.getList(query); this.list.hookToQuery(bookStreamCreator).subscribe((response) => { this.book = response; @@ -503,8 +498,8 @@ export class BookComponent implements OnInit { } ``` -* 我们注入了生成的 `BookService`. -* 我们实现了 [ListService](https://docs.abp.io/en/abp/latest/UI/Angular/List-Service),它是一个公用服务,提供了简单的分页,排序和搜索. +* 我们引入并注入了生成的 `BookService`. +* 我们使用 [ListService](../UI/Angular/List-Service.md),它是一个工具服务,提供了易用的分页,排序和搜索. 打开 `/src/app/book/book.component.html` 用以下内容替换它: @@ -545,9 +540,138 @@ export class BookComponent implements OnInit { 现在你可以在浏览器看到最终结果: -![Book list final result](./images/bookstore-book-list.png) +![图书列表最终结果](images/bookstore-book-list.png) -{{end}} +{{else if UI == "Blazor" || UI == "BlazorServer"}} + +## 创建图书页面 + +是时候创建可见和可用的东西了! 右击`Acme.BookStore.Blazor`项目下的`Pages`文件夹,新建一个名为`Books.razor`的**razor组件**. + +![blazor-add-books-component](images/blazor-add-books-component.png) + +用以下内容替换这个组件的内容: + +````html +@page "/books" + +

Books

+ +@code { + +} +```` + +### 将图书页面添加到主菜单 + +打开`Blazor`项目中的`BookStoreMenuContributor`类,在 `ConfigureMainMenuAsync` 方法的底部添加如下代码: + +````csharp +context.Menu.AddItem( + new ApplicationMenuItem( + "BooksStore", + l["Menu:BookStore"], + icon: "fa fa-book" + ).AddItem( + new ApplicationMenuItem( + "BooksStore.Books", + l["Menu:Books"], + url: "/books" + ) + ) +); +```` + +运行项目,使用用户名 `admin` 和密码 `1q2w3E*` 登录到应用程序. 看到新菜单项已添加到顶部栏: + +![blazor-menu-bookstore](images/blazor-menu-bookstore.png) + +点击BookStore下的Books子菜单项就会跳转到空的图书页面. + +### 图书列表 + +我们将使用[Blazorise library](https://blazorise.com/)作为UI组件.它是一个强大的库,支持主要的HTML/CSS框架,包括Bootstrap. + +ABP提供了一个通用的基类,`AbpCrudPageBase<...>`,用来创建CRUD风格的页面.这个基类兼容用来构建`IBookAppService`的`ICrudAppService`.所以我们从`AbpCrudPageBase`继承,获得标准CRUD的默认实现. + +打开`Books.razor` 并把内容修改成下面这样: + +````xml +@page "/books" +@using Volo.Abp.Application.Dtos +@using Acme.BookStore.Books +@using Acme.BookStore.Localization +@using Microsoft.Extensions.Localization +@inject IStringLocalizer L +@inherits AbpCrudPageBase + + + +

@L["Books"]

+
+ + + + + + + @L[$"Enum:BookType:{(int)context.Type}"] + + + + + @context.PublishDate.ToShortDateString() + + + + + + + @context.CreationTime.ToLongDateString() + + + + + +
+```` + +> 如果你可以编译并运行成功,但看到一些语法错误.你可以忽略这些错误,因为Visual Studio处理Blazor还有一些bug. + +* `AbpCrudPageBase`实现了所有的CRUD细节,我们从它继承. +* `Entities`, `TotalCount`, `PageSize`, `OnDataGridReadAsync`定义在基类中. +* 注入`IStringLocalizer` (作为`L`对象),用于本地化. + +虽然上面的代码非常容易理解,你仍然可以查看Blazorise [Card](https://blazorise.com/docs/components/card/)和[DataGrid](https://blazorise.com/docs/extensions/datagrid/)文档以更好地理解它们. + +#### 关于AbpCrudPageBase + +对于图书页面,我们将持续从`AbpCrudPageBase`获得益处. 你可以只注入`IBookAppService`并自己执行所有的服务端调用(感谢ABP的[动态C# HTTP API客户端代理](../API/Dynamic-CSharp-API-Clients.md)系统). + +## 运行最终应用程序 + +你可以运行应用程序!该部分的最终用户界面如下所示: + +![blazor-bookstore-book-list](images/blazor-bookstore-book-list.png) + +这是一个可以正常工作的,服务端分页,排序和本地化的图书列表. + +{{end # UI }} ## 下一章 diff --git a/docs/zh-Hans/Tutorials/Part-3.md b/docs/zh-Hans/Tutorials/Part-3.md index 90dd1326c3..6a9740730d 100644 --- a/docs/zh-Hans/Tutorials/Part-3.md +++ b/docs/zh-Hans/Tutorials/Part-3.md @@ -2,27 +2,10 @@ ````json //[doc-params] { - "UI": ["MVC","NG"], + "UI": ["MVC","Blazor","BlazorServer","NG"], "DB": ["EF","Mongo"] } ```` -{{ -if UI == "MVC" - UI_Text="mvc" -else if UI == "NG" - UI_Text="angular" -else - UI_Text="?" -end -if DB == "EF" - DB_Text="Entity Framework Core" -else if DB == "Mongo" - DB_Text="MongoDB" -else - DB_Text="?" -end -}} - ## 关于本教程 在本系列教程中, 你将构建一个名为 `Acme.BookStore` 的用于管理书籍及其作者列表的基于ABP的应用程序. 它是使用以下技术开发的: @@ -45,16 +28,30 @@ end ## 下载源码 -本教程根据你的**UI** 和 **Database**偏好有多个版,我们准备了两种可供下载的源码组合: +本教程根据你的**UI** 和 **数据库**偏好有多个版本,我们准备了几种可供下载的源码组合: * [MVC (Razor Pages) UI 与 EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) +* [Blazor UI 与 EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore) * [Angular UI 与 MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) +> 如果你在Windows中遇到 "文件名太长" or "解压错误", 很可能与Windows最大文件路径限制有关. Windows文件路径的最大长度为250字符. 为了解决这个问题,参阅 [在Windows 10中启用长路径](https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd#enable-long-paths-in-windows-10-version-1607-and-later). + +> 如果你遇到与Git相关的长路径错误, 尝试使用下面的命令在Windows中启用长路径. 参阅 https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path +> `git config --system core.longpaths true` + +{{if UI == "MVC" && DB == "EF"}} + +### 视频教程 + +本章也被录制为视频教程 **发布在YouTube**. + +{{end}} + {{if UI == "MVC"}} ## 创建新书籍 -通过本节, 你将会了解如何创建一个 modal form 来实现新增书籍的功能. 最终成果如下图所示: +通过本节, 你将会了解如何创建一个 modal form 实现新增书籍的功能. model dialog将如下图所示: ![bookstore-create-dialog](./images/bookstore-create-dialog-2.png) @@ -66,7 +63,7 @@ end #### CreateModal.cshtml.cs -打开 `CreateModal.cshtml.cs` 代码文件,用如下代码替换 `CreateModalModel` 类的实现: +打开 `CreateModal.cshtml.cs` 代码文件(`CreateModalModel` 类),替换成以下代码: ````C# using System.Threading.Tasks; @@ -101,10 +98,10 @@ namespace Acme.BookStore.Web.Pages.Books } ```` -* 该类派生于 `BookStorePageModel` 而非默认的 `PageModel`. `BookStorePageModel` 继承了 `PageModel` 并且添加了一些可以被你的page model类使用的通用属性和方法. +* 该类派生于 `BookStorePageModel` 而非默认的 `PageModel`. `BookStorePageModel` 间接继承了 `PageModel` 并且添加了一些可以被你的page model类使用的通用属性和方法. * `Book` 属性上的 `[BindProperty]` 特性将post请求提交上来的数据绑定到该属性上. * 该类通过构造函数注入了 `IBookAppService` 应用服务,并且在 `OnPostAsync` 处理程序中调用了服务的 `CreateAsync` 方法. -* 它在 `OnGet` 方法中创建一个新的 `CreateUpdateBookDto` 对象。 ASP.NET Core不需要像这样创建一个新实例就可以正常工作. 但是它不会为你创建实例,并且如果你的类在类构造函数中具有一些默认值分配或代码执行,它们将无法工作. 对于这种情况,我们为某些 `CreateUpdateBookDto` 属性设置了默认值. +* 它在 `OnGet` 方法中创建一个新的 `CreateUpdateBookDto` 对象。 ASP.NET Core不需要像这样创建一个新实例就可以正常工作. 但是它不会为你创建实例,并且如果你的类在类构造函数中赋值一些默认值或执行一些代码,它们将无法工作. 对于这种情况,我们为某些 `CreateUpdateBookDto` 属性设置了默认值. #### CreateModal.cshtml @@ -134,10 +131,9 @@ namespace Acme.BookStore.Web.Pages.Books * 这个 modal 使用 `abp-dynamic-form` [tag Helper](../UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md) 根据 `CreateBookViewModel` 类自动构建了表单. * `abp-model` 指定了 `Book` 属性为模型对象. -* `data-ajaxForm` 设置了表单通过AJAX提交,而不是经典的页面回发. * `abp-form-content` tag helper 作为表单控件渲染位置的占位符 (这是可选的,只有你在 `abp-dynamic-form` 中像本示例这样添加了其他内容才需要). -> 提示: 就像在本示例中一样,`Layout` 应该为 `null`,因为当通过AJAX加载模态时,我们不希望包括所有布局. +> 提示: 就像在本示例中一样,`Layout` 应该为 `null`,因为当通过AJAX加载模态窗口时,我们不希望包括所有布局. ### 添加 "New book" 按钮 @@ -195,9 +191,9 @@ namespace Acme.BookStore.Web.Pages.Books 如下图所示,只是在表格 **右上方** 添加了 **New book** 按钮: -![bookstore-new-book-button](./images/bookstore-new-book-button-2.png) +![bookstore-new-book-button](images/bookstore-new-book-button-2.png) -打开 `Pages/book/index.js` 在 `datatable` 配置代码后面添加如下代码: +打开 `Pages/Book/Index.js` 在 `datatable` 配置代码后面添加如下代码: ````js var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal'); @@ -212,9 +208,9 @@ $('#NewBookButton').click(function (e) { }); ```` -* `abp.ModalManager` 是一个在客户端打开和管理modal的辅助类.它基于Twitter Bootstrap的标准modal组件通过简化的API抽象隐藏了许多细节. +* `abp.ModalManager` 是一个在客户端管理modal的辅助类.它内部使用了Twitter Bootstrap的标准modal组件,但通过简化的API抽象了许多细节. * `createModal.onResult(...)` 用于在创建书籍后刷新数据表格. -* `createModal.open();` 用于打开模态创建新书籍. +* `createModal.open();` 用于打开modal创建新书籍. `Index.js` 的内容最终如下所示: @@ -335,8 +331,8 @@ namespace Acme.BookStore.Web.Pages.Books } ```` -* `[HiddenInput]` 和 `[BindProperty]` 是标准的 ASP.NET Core MVC 特性.这里启用 `SupportsGet` 从Http请求的查询字符串中获取Id的值. -* 在 `OnGetAsync` 方法中,将 `BookAppService.GetAsync` 方法返回的 `BookDto` 映射成 `CreateUpdateBookDto` 并赋值给Book属性. +* `[HiddenInput]` 和 `[BindProperty]` 是标准的 ASP.NET Core MVC 特性.这里启用 `SupportsGet` 从Http请求的查询字符串参数中获取Id的值. +* 在 `OnGetAsync` 方法中, 我们从 `BookAppService` 获得 `BookDto` ,并将它映射成DTO对象 `CreateUpdateBookDto`. * `OnPostAsync` 方法直接使用 `BookAppService.UpdateAsync` 来更新实体. ### BookDto 到 CreateUpdateBookDto 对象映射 @@ -391,10 +387,10 @@ namespace Acme.BookStore.Web 这个页面内容和 `CreateModal.cshtml` 非常相似,除了以下几点: -* 它包含`id`属性的`abp-input`, 用于存储编辑书的 `id` (它是隐藏的Input) +* 它包含`id`属性的`abp-input`, 用于存储被编辑书籍的 `id` (它是隐藏的Input) * 此页面指定的post地址是`Books/EditModal`. -### 为表格添加 "操作(Actions)" 下拉菜单 +### 为表格添加 "操作(Actions)" 下拉菜单 我们将为表格每行添加下拉按钮 ("Actions"): @@ -516,9 +512,9 @@ $(function () { } ```` -* `confirmMessage` 用来在实际执行 `action` 之前向用户进行确认. -* 通过javascript代理方法 `acme.bookStore.books.book.delete(...)` 执行一个AJAX请求来删除一个book实体. -* `abp.notify.info` 用来在执行删除操作后显示一个toastr通知信息. +* `confirmMessage` 执行 `action` 前向用户进行确认. +* `acme.bookStore.books.book.delete(...)` 执行一个AJAX请求删除一个book. +* `abp.notify.info` 执行删除操作后显示一个通知信息. 由于我们使用了两个新的本地化文本(`BookDeletionConfirmationMessage`和`SuccesslyDeleted`),因此你需要将它们添加到本地化文件(`Acme.BookStore.Domain.Shared`项目的`Localization/BookStore`文件夹下的`en.json`): @@ -640,7 +636,7 @@ $(function () { ## 创建新书籍 -下面的章节中,你将学习到如何创建一个新的模态对话框来新增书籍. +下面的章节中,你将学习到如何创建一个新的模态窗口新增书籍. ### BookComponent @@ -649,8 +645,7 @@ $(function () { ```js import { ListService, PagedResultDto } from '@abp/ng.core'; import { Component, OnInit } from '@angular/core'; -import { BookDto } from './models'; -import { BookService } from './services'; +import { BookService, BookDto } from '@proxy/books'; @Component({ selector: 'app-book', @@ -666,7 +661,7 @@ export class BookComponent implements OnInit { constructor(public readonly list: ListService, private bookService: BookService) {} ngOnInit() { - const bookStreamCreator = (query) => this.bookService.getListByInput(query); + const bookStreamCreator = (query) => this.bookService.getList(query); this.list.hookToQuery(bookStreamCreator).subscribe((response) => { this.book = response; @@ -680,7 +675,8 @@ export class BookComponent implements OnInit { } ``` -* 我们定义了一个名为 `isModalOpen` 的变量和 `createBook` 方法. +* 我们定义了一个名为 `isModalOpen` 的属性和 `createBook` 方法. + 打开 `/src/app/book/book.component.html` 做以下更改: @@ -690,9 +686,9 @@ export class BookComponent implements OnInit {
{%{{{ '::Menu:Books' | abpLocalization }}}%}
-
+
- +
- +
@@ -726,11 +722,11 @@ export class BookComponent implements OnInit { ``` * 添加了 `New book` 按钮到卡片头部. -* 添加了 `abp-modal` 渲染模态框,允许用户创建新书. `abp-modal` 是显示模态框的预构建组件. 你也可以使用其它方法显示模态框,但 `abp-modal` 提供了一些附加的好处. +* 添加了 `abp-modal` 渲染模态框,允许用户创建新书. `abp-modal` 是显示模态框的预构建组件. 你也可以使用其它方法显示模态框,但 `abp-modal` 提供了一些额外的好处. 你可以打开浏览器,点击**New book**按钮看到模态框. -![Empty modal for new book](./images/bookstore-empty-new-book-modal.png) +![Empty modal for new book](images/bookstore-empty-new-book-modal.png) ### 添加响应式表单 @@ -741,8 +737,7 @@ export class BookComponent implements OnInit { ```js import { ListService, PagedResultDto } from '@abp/ng.core'; import { Component, OnInit } from '@angular/core'; -import { BookDto, BookType } from './models'; // add BookType -import { BookService } from './services'; +import { BookService, BookDto, bookTypeOptions } from '@proxy/books'; // add bookTypeOptions import { FormGroup, FormBuilder, Validators } from '@angular/forms'; // add this @Component({ @@ -756,12 +751,8 @@ export class BookComponent implements OnInit { form: FormGroup; // add this line - bookType = BookType; // add this line - // add bookTypes as a list of BookType enum members - bookTypes = Object.keys(this.bookType).filter( - (key) => typeof this.bookType[key] === 'number' - ); + bookTypes = bookTypeOptions; isModalOpen = false; @@ -772,7 +763,7 @@ export class BookComponent implements OnInit { ) {} ngOnInit() { - const bookStreamCreator = (query) => this.bookService.getListByInput(query); + const bookStreamCreator = (query) => this.bookService.getList(query); this.list.hookToQuery(bookStreamCreator).subscribe((response) => { this.book = response; @@ -800,7 +791,7 @@ export class BookComponent implements OnInit { return; } - this.bookService.createByInput(this.form.value).subscribe(() => { + this.bookService.create(this.form.value).subscribe(() => { this.isModalOpen = false; this.form.reset(); this.list.get(); @@ -809,12 +800,11 @@ export class BookComponent implements OnInit { } ``` -* 导入了 `FormGroup, FormBuilder and Validators`. +* 从` @angular/forms `导入了 `FormGroup, FormBuilder and Validators`. * 添加了 `form: FormGroup` 变量. -* 添加了 `bookType` 属性,你可以从模板中获取 `BookType` 枚举成员. * 添加了 `bookTypes` 属性作为 `BookType` 枚举成员列表. 将在表单选项中使用. -* 我们注入了 `fb: FormBuilder` 服务到构造函数. [FormBuilder](https://angular.io/api/forms/FormBuilder) 服务为生成控件提供了方便的方法. 它减少了构建复杂表单所需的样板文件的数量. -* 我们添加了 `buildForm` 方法到文件末尾, 在 `createBook` 方法调用 `buildForm()` 方法. 该方法创建一个响应式表单去创建新书. +* 我们注入了 `FormBuilder` 到构造函数. [FormBuilder](https://angular.io/api/forms/FormBuilder) 提供了简便的方法生成表单控件. 它减少了构建复杂表单所需的样板文件的数量. +* 我们添加了 `buildForm` 方法到文件末尾, 在 `createBook` 方法调用 `buildForm()` 方法. * 添加了`save` 方法. 打开 `/src/app/book/book.component.html`,使用以下内容替换 ` `: @@ -836,7 +826,7 @@ export class BookComponent implements OnInit { * @@ -897,13 +887,12 @@ export class BookModule { } * 我们导入了 `NgbDatepickerModule` 来使用日期选择器. -打开 `/src/app/book/book.component.ts` 使用以内内容替换: +打开 `/src/app/book/book.component.ts` 使用以下内容替换: ```js import { ListService, PagedResultDto } from '@abp/ng.core'; import { Component, OnInit } from '@angular/core'; -import { BookDto, BookType } from './models'; -import { BookService } from './services'; +import { BookService, BookDto, bookTypeOptions } from '@proxy/books'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; // added this line @@ -923,11 +912,7 @@ export class BookComponent implements OnInit { form: FormGroup; - bookType = BookType; - - bookTypes = Object.keys(this.bookType).filter( - (key) => typeof this.bookType[key] === 'number' - ); + bookTypes = bookTypeOptions; isModalOpen = false; @@ -938,7 +923,7 @@ export class BookComponent implements OnInit { ) {} ngOnInit() { - const bookStreamCreator = (query) => this.bookService.getListByInput(query); + const bookStreamCreator = (query) => this.bookService.getList(query); this.list.hookToQuery(bookStreamCreator).subscribe((response) => { this.book = response; @@ -964,7 +949,7 @@ export class BookComponent implements OnInit { return; } - this.bookService.createByInput(this.form.value).subscribe(() => { + this.bookService.create(this.form.value).subscribe(() => { this.isModalOpen = false; this.form.reset(); this.list.get(); @@ -974,11 +959,11 @@ export class BookComponent implements OnInit { ``` * 导入了 `NgbDateNativeAdapter` 和 `NgbDateAdapter`. -* 我们添加了一个新的 `NgbDateAdapter` 提供程序,它将Datepicker值转换为 `Date` 类型. 有关更多详细信息,请参见[datepicker adapters](https://ng-bootstrap.github.io/#/components/datepicker/overview). +* 我们添加了一个新的 `NgbDateAdapter` 提供程序,它将Datepicker值转换为 `Date` 类型. 更多详细信息,请参见[datepicker adapters](https://ng-bootstrap.github.io/#/components/datepicker/overview). 现在你可以打开浏览器看到以下变化: -![Save button to the modal](./images/bookstore-new-book-form-v2.png) +![Save button to the modal](images/bookstore-new-book-form-v2.png) ## 更新书籍 @@ -987,8 +972,7 @@ export class BookComponent implements OnInit { ```js import { ListService, PagedResultDto } from '@abp/ng.core'; import { Component, OnInit } from '@angular/core'; -import { BookDto, BookType, CreateUpdateBookDto } from './models'; -import { BookService } from './services'; +import { BookService, BookDto, bookTypeOptions } from '@proxy/books'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; @@ -1001,15 +985,11 @@ import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap export class BookComponent implements OnInit { book = { items: [], totalCount: 0 } as PagedResultDto; - selectedBook = new BookDto(); // declare selectedBook + selectedBook = {} as BookDto; // declare selectedBook form: FormGroup; - bookType = BookType; - - bookTypes = Object.keys(this.bookType).filter( - (key) => typeof this.bookType[key] === 'number' - ); + bookTypes = bookTypeOptions; isModalOpen = false; @@ -1020,7 +1000,7 @@ export class BookComponent implements OnInit { ) {} ngOnInit() { - const bookStreamCreator = (query) => this.bookService.getListByInput(query); + const bookStreamCreator = (query) => this.bookService.getList(query); this.list.hookToQuery(bookStreamCreator).subscribe((response) => { this.book = response; @@ -1028,14 +1008,14 @@ export class BookComponent implements OnInit { } createBook() { - this.selectedBook = new BookDto(); // reset the selected book + this.selectedBook = {} as BookDto; // reset the selected book this.buildForm(); this.isModalOpen = true; } // Add editBook method editBook(id: string) { - this.bookService.getById(id).subscribe((book) => { + this.bookService.get(id).subscribe((book) => { this.selectedBook = book; this.buildForm(); this.isModalOpen = true; @@ -1061,8 +1041,8 @@ export class BookComponent implements OnInit { } const request = this.selectedBook.id - ? this.bookService.updateByIdAndInput(this.form.value, this.selectedBook.id) - : this.bookService.createByInput(this.form.value); + ? this.bookService.update(this.selectedBook.id, this.form.value) + : this.bookService.create(this.form.value); request.subscribe(() => { this.isModalOpen = false; @@ -1074,10 +1054,10 @@ export class BookComponent implements OnInit { ``` * 我们声明了类型为 `BookDto` 的 `selectedBook` 变量. -* 我们添加了 `editBook` 方法, 根据给定图书 `Id` 设置 `selectedBook` 对象. +* 我们添加了 `editBook` 方法, 根据给定书籍 `Id` 设置 `selectedBook` 对象. * 我们替换了 `buildForm` 方法使用 `selectedBook` 数据创建表单. * 我们替换了 `createBook` 方法,设置 `selectedBook` 为空对象. -* 我们替换了 `save` 方法. +* 我们修改了 `save` 方法,同时处理新建和更新操作. ### 添加 "Actions" 下拉框到表格 @@ -1111,7 +1091,7 @@ export class BookComponent implements OnInit { 在表格的第一列添加了一个 "Actions" 下拉菜单,如下图所示: -![Action buttons](./images/bookstore-actions-buttons.png) +![Action buttons](images/bookstore-actions-buttons.png) 同时如下所示更改 `ng-template #abpHeader` 部分: @@ -1147,13 +1127,13 @@ constructor( delete(id: string) { this.confirmation.warn('::AreYouSureToDelete', '::AreYouSure').subscribe((status) => { if (status === Confirmation.Status.confirm) { - this.bookService.deleteById(id).subscribe(() => this.list.get()); + this.bookService.delete(id).subscribe(() => this.list.get()); } }); } ``` -* 我们注入了 `ConfirmationService`. +* 我们引入了 `ConfirmationService`. * 我们注入了 `ConfirmationService` 到构造函数. * 添加了 `delete` 方法. @@ -1161,6 +1141,7 @@ delete(id: string) { ### 添加删除按钮: + 打开 `/src/app/book/book.component.html` 修改 `ngbDropdownMenu` 添加删除按钮: ```html @@ -1174,14 +1155,445 @@ delete(id: string) { 最终操作下拉框UI看起来如下: -![bookstore-final-actions-dropdown](./images/bookstore-final-actions-dropdown.png) +![bookstore-final-actions-dropdown](images/bookstore-final-actions-dropdown.png) + +点击 `delete` 操作调用 `delete` 方法,然后显示一个确认弹层如下图所示. + +![bookstore-confirmation-popup](images/bookstore-confirmation-popup.png) + +{{end}} + +{{if UI == "Blazor" || UI == "BlazorServer"}} + +## 创建新书籍 + +通过本节, 你将会了解如何创建一个模态窗口实现新增书籍的功能. 因为我们已经从 `AbpCrudPageBase` 继承, 所以只需要开发视图部分. + +### 添加 "New Button" 按钮 + +打开 `Books.razor` 替换 `` 部分为以下代码: + +````xml + + + +

@L["Books"]

+
+ + + +
+
+```` + +如下图所示,卡片头 **右侧** 添加了 **New book** 按钮: + +![blazor-add-book-button](images/blazor-add-book-button.png) + +现在, 我们可以添加点击按钮后打开的模态窗口了. + +### 书籍创建模态窗口 + +打开 `Books.razor`, 添加以下代码到页面底部: + +````xml + + + +
+ + @L["NewBook"] + + + + + + + @L["Name"] + + + + + + + + + @L["Type"] + + + + @L["PublishDate"] + + + + @L["Price"] + + + + + + + + +
+
+
+```` + +这段代码需要一个服务; 在文件顶部, `@inherits...` 行前, 注入 `AbpBlazorMessageLocalizerHelper`: + +````csharp +@inject AbpBlazorMessageLocalizerHelper LH +```` + +* 表单实现了验证功能, `AbpBlazorMessageLocalizerHelper` 用于本地化验证消息. +* `CreateModal` 对象, `CloseCreateModalAsync` 和 `CreateEntityAsync` 方法定义在基类中. 参阅 [Blazorise文档](https://blazorise.com/docs/) 以深入理解 `Modal` 和其它组件. + +这就是全部了. 运行应用程序, 尝试添加一本新书. + +![blazor-new-book-modal](images/blazor-new-book-modal.png) + +## 更新书籍 + +编辑书籍与新建书籍很类似. -点击 `delete` 动作调用 `delete` 方法,然后无法显示一个确认弹层如下图所示. +### 操作下拉菜单 -![bookstore-confirmation-popup](./images/bookstore-confirmation-popup.png) +打开 `Books.razor` , 在 `DataGridColumns` 中添加以下 `DataGridEntityActionsColumn` 作为第一项: + +````xml + + + + + + + +```` + +* `OpenEditModalAsync` 定义在基类中, 它接收实体(书籍)参数, 编辑这个实体. + +`DataGridEntityActionsColumn` 组件用于显示 `DataGrid` 每一行中的"操作" 下拉菜单. 如果其中只有唯一的操作, `DataGridEntityActionsColumn` 显示 **唯一按钮**, 而不是下拉菜单. + +![blazor-edit-book-action](images/blazor-edit-book-action-2.png) + +### 编辑模态窗口 + +我们现在可以定义一个模态窗口编辑书籍. 加入下面的代码到 `Books.razor` 页面的底部: + +````xml + + + +
+ + @EditingEntity.Name + + + + + + + @L["Name"] + + + + + + + + + @L["Type"] + + + + @L["PublishDate"] + + + + @L["Price"] + + + + + + + + +
+
+
+```` + +### AutoMapper 配置 + +基类 `AbpCrudPageBase` 使用 [对象到对象映射](../Object-To-Object-Mapping.md) 系统将 `BookDto` 对象转化为`CreateUpdateBookDto` 对象. 因此, 我们需要定义映射. + +打开 `Acme.BookStore.Blazor` 项目中的 `BookStoreBlazorAutoMapperProfile `, 替换成以下内容: + +````csharp +using Acme.BookStore.Books; +using AutoMapper; + +namespace Acme.BookStore.Blazor +{ + public class BookStoreBlazorAutoMapperProfile : Profile + { + public BookStoreBlazorAutoMapperProfile() + { + CreateMap(); + } + } +} +```` + +* `CreateMap();` 行用于定义映射. + +### 测试编辑模态窗口 + +你可以运行程序并尝试编辑一本书. + +![blazor-edit-book-modal](images/blazor-edit-book-modal.png) + +> 提示: 尝试保留 *Name* 字段为空并提交表单, 将显示验证错误消息. + +## 删除书籍 + +打开 `Books.razor` 页面, 在 `EntityActions` 中的"编辑" 操作下面加入以下的 `EntityAction`: + +````xml + +```` + +* `DeleteEntityAsync` 定义在基类中. 通过向服务器发起请求删除实体. +* `ConfirmationMessage` 执行操作前显示确认消息的回调函数. +* `GetDeleteConfirmationMessage` 定义在基类中. 你可以覆写这个方法 (或传递其它值给 `ConfirmationMessage` 参数) 以定制本地化消息. + +因为"操作" 按钮现在有了两个操作, 变成了下拉菜单: + +![blazor-edit-book-action](images/blazor-delete-book-action.png) + +运行程序并尝试删除一本书. + +## 完整的 CRUD UI 代码 + +下面是完整的创建图书管理CRUD页面的代码, 这些代码在上面是分成两部分开发的: + +````xml +@page "/books" +@using Volo.Abp.Application.Dtos +@using Acme.BookStore.Books +@using Acme.BookStore.Localization +@using Microsoft.Extensions.Localization +@using Volo.Abp.AspNetCore.Components.Web +@inject IStringLocalizer L +@inject AbpBlazorMessageLocalizerHelper LH +@inherits AbpCrudPageBase + + + + + +

@L["Books"]

+
+ + + +
+
+ + + + + + + + + + + + + + + @L[$"Enum:BookType:{(int) context.Type}"] + + + + + @context.PublishDate.ToShortDateString() + + + + + + + @context.CreationTime.ToLongDateString() + + + + + +
+ + + + +
+ + @L["NewBook"] + + + + + + + @L["Name"] + + + + + + + + + @L["Type"] + + + + @L["PublishDate"] + + + + @L["Price"] + + + + + + + + +
+
+
+ + + + +
+ + @EditingEntity.Name + + + + + + + @L["Name"] + + + + + + + + + @L["Type"] + + + + @L["PublishDate"] + + + + @L["Price"] + + + + + + + + +
+
+
+```` {{end}} ## 下一章 -查看本教程的[下一章](Part-4.md). +查看本教程的[下一章](Part-4.md). \ No newline at end of file diff --git a/docs/zh-Hans/Tutorials/Todo/Index.md b/docs/zh-Hans/Tutorials/Todo/Index.md index d940957c18..2200bf23e6 100644 --- a/docs/zh-Hans/Tutorials/Todo/Index.md +++ b/docs/zh-Hans/Tutorials/Todo/Index.md @@ -682,7 +682,7 @@ ABP提供了一个便捷的功能来自动创建客户端服务, 以方便地使 当启动 `TodoApp.HttpApi.Host` 项目后, 在`angular`文件夹中打开一个命令行终端并输入以下命令: ````bash -abp generate-proxy +abp generate-proxy -t ng ```` 如果一切顺利, 它应该生成如下输出: diff --git a/docs/zh-Hans/Tutorials/images/blazor-add-book-button.png b/docs/zh-Hans/Tutorials/images/blazor-add-book-button.png new file mode 100644 index 0000000000..58a607d3b1 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/blazor-add-book-button.png differ diff --git a/docs/zh-Hans/Tutorials/images/blazor-add-books-component.png b/docs/zh-Hans/Tutorials/images/blazor-add-books-component.png new file mode 100644 index 0000000000..ec0412d31e Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/blazor-add-books-component.png differ diff --git a/docs/zh-Hans/Tutorials/images/blazor-bookstore-book-list.png b/docs/zh-Hans/Tutorials/images/blazor-bookstore-book-list.png new file mode 100644 index 0000000000..18bb26ccb1 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/blazor-bookstore-book-list.png differ diff --git a/docs/zh-Hans/Tutorials/images/blazor-delete-book-action.png b/docs/zh-Hans/Tutorials/images/blazor-delete-book-action.png new file mode 100644 index 0000000000..f2b0b83f30 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/blazor-delete-book-action.png differ diff --git a/docs/zh-Hans/Tutorials/images/blazor-edit-book-action-2.png b/docs/zh-Hans/Tutorials/images/blazor-edit-book-action-2.png new file mode 100644 index 0000000000..c3e2b1e0e1 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/blazor-edit-book-action-2.png differ diff --git a/docs/zh-Hans/Tutorials/images/blazor-edit-book-modal.png b/docs/zh-Hans/Tutorials/images/blazor-edit-book-modal.png new file mode 100644 index 0000000000..e63e796d34 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/blazor-edit-book-modal.png differ diff --git a/docs/zh-Hans/Tutorials/images/blazor-menu-bookstore.png b/docs/zh-Hans/Tutorials/images/blazor-menu-bookstore.png new file mode 100644 index 0000000000..f9f334aa1d Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/blazor-menu-bookstore.png differ diff --git a/docs/zh-Hans/Tutorials/images/blazor-new-book-modal.png b/docs/zh-Hans/Tutorials/images/blazor-new-book-modal.png new file mode 100644 index 0000000000..dd284a0ffd Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/blazor-new-book-modal.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-efcore-migration.png b/docs/zh-Hans/Tutorials/images/bookstore-efcore-migration.png index 9714e795e2..5c034f6e80 100644 Binary files a/docs/zh-Hans/Tutorials/images/bookstore-efcore-migration.png and b/docs/zh-Hans/Tutorials/images/bookstore-efcore-migration.png differ diff --git a/docs/zh-Hans/Tutorials/images/generated-proxies-3.png b/docs/zh-Hans/Tutorials/images/generated-proxies-3.png new file mode 100644 index 0000000000..2a94f4e389 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/generated-proxies-3.png differ diff --git a/docs/zh-Hans/Tutorials/images/vs-run-without-iisexpress.png b/docs/zh-Hans/Tutorials/images/vs-run-without-iisexpress.png new file mode 100644 index 0000000000..03c30e2b48 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/vs-run-without-iisexpress.png differ diff --git a/docs/zh-Hans/UI/Angular/Service-Proxies.md b/docs/zh-Hans/UI/Angular/Service-Proxies.md index 2824bc44a7..cc07a24175 100644 --- a/docs/zh-Hans/UI/Angular/Service-Proxies.md +++ b/docs/zh-Hans/UI/Angular/Service-Proxies.md @@ -15,7 +15,7 @@ ABP CLI 的`generate-proxies` 命令在 `src/app` 文件夹中创建按模块名 在angular应用程序的**根文件夹**中运行以下命令: ```bash -abp generate-proxy +abp generate-proxy -t ng ``` 它只为你自己的应用程序的服务创建代理. 不会为你正在使用的应用程序模块的服务创建代理(默认情况下). 有几个选项,参见[CLI文档](../../CLI). diff --git a/docs/zh-Hans/images/create-aspnet-core-application.png b/docs/zh-Hans/images/create-aspnet-core-application.png index 5813cf38e9..b8b98f5c32 100644 Binary files a/docs/zh-Hans/images/create-aspnet-core-application.png and b/docs/zh-Hans/images/create-aspnet-core-application.png differ diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClient.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClient.cs index 92152ac807..fba7a3fdfe 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClient.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClient.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.AspNetCore.Http; using System.Threading.Tasks; using Microsoft.Extensions.Caching.Distributed; @@ -45,9 +45,8 @@ public class MvcCachedApplicationConfigurationClient : ICachedApplicationConfigu var cacheKey = CreateCacheKey(); var httpContext = HttpContextAccessor?.HttpContext; - if (httpContext != null && !httpContext.WebSockets.IsWebSocketRequest && httpContext.Items[cacheKey] is ApplicationConfigurationDto configuration) + if (httpContext != null && httpContext.Items[cacheKey] is ApplicationConfigurationDto configuration) { - return configuration; } @@ -58,10 +57,10 @@ public class MvcCachedApplicationConfigurationClient : ICachedApplicationConfigu () => new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(300) //TODO: Should be configurable. - } + } ); - if (httpContext != null && !httpContext.WebSockets.IsWebSocketRequest) + if (httpContext != null) { httpContext.Items[cacheKey] = configuration; } @@ -74,7 +73,7 @@ public class MvcCachedApplicationConfigurationClient : ICachedApplicationConfigu var cacheKey = CreateCacheKey(); var httpContext = HttpContextAccessor?.HttpContext; - if (httpContext != null && !httpContext.WebSockets.IsWebSocketRequest && httpContext.Items[cacheKey] is ApplicationConfigurationDto configuration) + if (httpContext != null && httpContext.Items[cacheKey] is ApplicationConfigurationDto configuration) { return configuration; } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/AbpRemoteStreamContentModelBinder.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/AbpRemoteStreamContentModelBinder.cs index 000716fa62..ea633f569a 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/AbpRemoteStreamContentModelBinder.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/AbpRemoteStreamContentModelBinder.cs @@ -8,7 +8,8 @@ using Volo.Abp.Content; namespace Volo.Abp.AspNetCore.Mvc.ContentFormatters; -public class AbpRemoteStreamContentModelBinder : IModelBinder +public class AbpRemoteStreamContentModelBinder : IModelBinder + where TRemoteStreamContent: class, IRemoteStreamContent { public async Task BindModelAsync(ModelBindingContext bindingContext) { @@ -17,7 +18,7 @@ public class AbpRemoteStreamContentModelBinder : IModelBinder throw new ArgumentNullException(nameof(bindingContext)); } - var postedFiles = new List(); + var postedFiles = GetCompatibleCollection(bindingContext); // If we're at the top level, then use the FieldName (parameter or property name). // This handles the fact that there will be nothing in the ValueProviders for this parameter @@ -42,7 +43,7 @@ public class AbpRemoteStreamContentModelBinder : IModelBinder } object value; - if (bindingContext.ModelType == typeof(IRemoteStreamContent) || bindingContext.ModelType == typeof(RemoteStreamContent)) + if (bindingContext.ModelType == typeof(TRemoteStreamContent)) { if (postedFiles.Count == 0) { @@ -63,7 +64,7 @@ public class AbpRemoteStreamContentModelBinder : IModelBinder // Perform any final type mangling needed. var modelType = bindingContext.ModelType; - if (modelType == typeof(IRemoteStreamContent[]) || modelType == typeof(RemoteStreamContent[])) + if (modelType == typeof(TRemoteStreamContent[])) { value = postedFiles.ToArray(); } @@ -91,7 +92,7 @@ public class AbpRemoteStreamContentModelBinder : IModelBinder private async Task GetFormFilesAsync( string modelName, ModelBindingContext bindingContext, - ICollection postedFiles) + ICollection postedFiles) { var request = bindingContext.HttpContext.Request; if (request.HasFormContentType) @@ -108,13 +109,52 @@ public class AbpRemoteStreamContentModelBinder : IModelBinder if (file.Name.Equals(modelName, StringComparison.OrdinalIgnoreCase)) { - postedFiles.Add(new RemoteStreamContent(file.OpenReadStream(), file.FileName, file.ContentType, file.Length)); + postedFiles.Add(new RemoteStreamContent(file.OpenReadStream(), file.FileName, file.ContentType, file.Length).As()); } } } else if (bindingContext.IsTopLevelObject) { - postedFiles.Add(new RemoteStreamContent(request.Body, null, request.ContentType, request.ContentLength)); + postedFiles.Add(new RemoteStreamContent(request.Body, null, request.ContentType, request.ContentLength).As()); } } + + private static ICollection GetCompatibleCollection(ModelBindingContext bindingContext) + { + var model = bindingContext.Model; + var modelType = bindingContext.ModelType; + + // There's a limited set of collection types we can create here. + // + // For the simple cases: Choose List if the destination type supports it (at least as an intermediary). + // + // For more complex cases: If the destination type is a class that implements ICollection, then activate + // an instance and return that. + // + // Otherwise just give up. + if (typeof(T).IsAssignableFrom(modelType)) + { + return new List(); + } + + if (modelType == typeof(T[])) + { + return new List(); + } + + // Does collection exist and can it be reused? + if (model is ICollection collection && !collection.IsReadOnly) + { + collection.Clear(); + + return collection; + } + + if (modelType.IsAssignableFrom(typeof(List))) + { + return new List(); + } + + return (ICollection)Activator.CreateInstance(modelType); + } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/AbpRemoteStreamContentModelBinderProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/AbpRemoteStreamContentModelBinderProvider.cs index ef49dc89f6..7892e6a1c2 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/AbpRemoteStreamContentModelBinderProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/AbpRemoteStreamContentModelBinderProvider.cs @@ -14,12 +14,16 @@ public class AbpRemoteStreamContentModelBinderProvider : IModelBinderProvider throw new ArgumentNullException(nameof(context)); } - if (context.Metadata.ModelType == typeof(IRemoteStreamContent) || - context.Metadata.ModelType == typeof(RemoteStreamContent) || - typeof(IEnumerable).IsAssignableFrom(context.Metadata.ModelType) || + if (context.Metadata.ModelType == typeof(RemoteStreamContent) || typeof(IEnumerable).IsAssignableFrom(context.Metadata.ModelType)) { - return new AbpRemoteStreamContentModelBinder(); + return new AbpRemoteStreamContentModelBinder(); + } + + if (context.Metadata.ModelType == typeof(IRemoteStreamContent) || + typeof(IEnumerable).IsAssignableFrom(context.Metadata.ModelType)) + { + return new AbpRemoteStreamContentModelBinder(); } return null; diff --git a/framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/AbpAspNetCoreServiceCollectionExtensions.cs b/framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/AbpAspNetCoreServiceCollectionExtensions.cs index cc88cac891..bda87a4e32 100644 --- a/framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/AbpAspNetCoreServiceCollectionExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/AbpAspNetCoreServiceCollectionExtensions.cs @@ -1,4 +1,6 @@ -using Microsoft.AspNetCore.Hosting; +using System.Linq; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; namespace Microsoft.Extensions.DependencyInjection; @@ -6,6 +8,16 @@ public static class AbpAspNetCoreServiceCollectionExtensions { public static IWebHostEnvironment GetHostingEnvironment(this IServiceCollection services) { - return services.GetSingletonInstance(); + var hostingEnvironment = services.GetSingletonInstanceOrNull(); + + if (hostingEnvironment == null) + { + return new EmptyHostingEnvironment() + { + EnvironmentName = Environments.Development + }; + } + + return hostingEnvironment; } } diff --git a/framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/EmptyHostingEnvironment.cs b/framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/EmptyHostingEnvironment.cs new file mode 100644 index 0000000000..371162f0da --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/EmptyHostingEnvironment.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.FileProviders; + +namespace Microsoft.Extensions.DependencyInjection; + +internal class EmptyHostingEnvironment : IWebHostEnvironment +{ + public string EnvironmentName { get; set; } + + public string ApplicationName { get; set; } + + public string WebRootPath { get; set; } + + public IFileProvider WebRootFileProvider { get; set; } + + public string ContentRootPath { get; set; } + + public IFileProvider ContentRootFileProvider { get; set; } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs index 303acd7e00..83a9352faf 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs @@ -41,29 +41,28 @@ public class AbpCliCoreModule : AbpModule Configure(options => { - //TODO: Define constants like done for GenerateProxyCommand.Name. - options.Commands["help"] = typeof(HelpCommand); - options.Commands["prompt"] = typeof(PromptCommand); - options.Commands["new"] = typeof(NewCommand); - options.Commands["get-source"] = typeof(GetSourceCommand); - options.Commands["update"] = typeof(UpdateCommand); - options.Commands["add-package"] = typeof(AddPackageCommand); - options.Commands["add-module"] = typeof(AddModuleCommand); - options.Commands["list-modules"] = typeof(ListModulesCommand); - options.Commands["login"] = typeof(LoginCommand); - options.Commands["login-info"] = typeof(LoginInfoCommand); - options.Commands["logout"] = typeof(LogoutCommand); + options.Commands[HelpCommand.Name] = typeof(HelpCommand); + options.Commands[PromptCommand.Name] = typeof(PromptCommand); + options.Commands[NewCommand.Name] = typeof(NewCommand); + options.Commands[GetSourceCommand.Name] = typeof(GetSourceCommand); + options.Commands[UpdateCommand.Name] = typeof(UpdateCommand); + options.Commands[AddPackageCommand.Name] = typeof(AddPackageCommand); + options.Commands[AddModuleCommand.Name] = typeof(AddModuleCommand); + options.Commands[ListModulesCommand.Name] = typeof(ListModulesCommand); + options.Commands[LoginCommand.Name] = typeof(LoginCommand); + options.Commands[LoginInfoCommand.Name] = typeof(LoginInfoCommand); + options.Commands[LogoutCommand.Name] = typeof(LogoutCommand); options.Commands[GenerateProxyCommand.Name] = typeof(GenerateProxyCommand); options.Commands[RemoveProxyCommand.Name] = typeof(RemoveProxyCommand); - options.Commands["suite"] = typeof(SuiteCommand); - options.Commands["switch-to-preview"] = typeof(SwitchToPreviewCommand); - options.Commands["switch-to-stable"] = typeof(SwitchToStableCommand); - options.Commands["switch-to-nightly"] = typeof(SwitchToNightlyCommand); - options.Commands["translate"] = typeof(TranslateCommand); - options.Commands["build"] = typeof(BuildCommand); - options.Commands["bundle"] = typeof(BundleCommand); - options.Commands["create-migration-and-run-migrator"] = typeof(CreateMigrationAndRunMigratorCommand); - options.Commands["install-libs"] = typeof(InstallLibsCommand); + options.Commands[SuiteCommand.Name] = typeof(SuiteCommand); + options.Commands[SwitchToPreviewCommand.Name] = typeof(SwitchToPreviewCommand); + options.Commands[SwitchToStableCommand.Name] = typeof(SwitchToStableCommand); + options.Commands[SwitchToNightlyCommand.Name] = typeof(SwitchToNightlyCommand); + options.Commands[TranslateCommand.Name] = typeof(TranslateCommand); + options.Commands[BuildCommand.Name] = typeof(BuildCommand); + options.Commands[BundleCommand.Name] = typeof(BundleCommand); + options.Commands[CreateMigrationAndRunMigratorCommand.Name] = typeof(CreateMigrationAndRunMigratorCommand); + options.Commands[InstallLibsCommand.Name] = typeof(InstallLibsCommand); }); Configure(options => diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs index df38d7c57b..df81fe4cb7 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs @@ -14,6 +14,8 @@ namespace Volo.Abp.Cli.Commands; public class AddModuleCommand : IConsoleCommand, ITransientDependency { + public const string Name = "add-module"; + private AddModuleInfoOutput _lastAddedModuleInfo; public ILogger Logger { get; set; } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs index 41d9824c6b..d21730dbb8 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs @@ -14,6 +14,8 @@ namespace Volo.Abp.Cli.Commands; public class AddPackageCommand : IConsoleCommand, ITransientDependency { + public const string Name = "add-package"; + public ILogger Logger { get; set; } protected ProjectNugetPackageAdder ProjectNugetPackageAdder { get; } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/BuildCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/BuildCommand.cs index 52c7fdd56c..327b737e89 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/BuildCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/BuildCommand.cs @@ -12,6 +12,8 @@ namespace Volo.Abp.Cli.Commands; public class BuildCommand : IConsoleCommand, ITransientDependency { + public const string Name = "build"; + public IDotNetProjectDependencyFiller DotNetProjectDependencyFiller { get; set; } public IChangedProjectFinder ChangedProjectFinder { get; set; } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/BundleCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/BundleCommand.cs index 5367e7461a..e758d22dd3 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/BundleCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/BundleCommand.cs @@ -11,6 +11,8 @@ namespace Volo.Abp.Cli.Commands; public class BundleCommand : IConsoleCommand, ITransientDependency { + public const string Name = "bundle"; + public ILogger Logger { get; set; } public IBundlingService BundlingService { get; set; } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CreateMigrationAndRunMigratorCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CreateMigrationAndRunMigratorCommand.cs index a0ffbeacea..3b6fbefe98 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CreateMigrationAndRunMigratorCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CreateMigrationAndRunMigratorCommand.cs @@ -12,6 +12,8 @@ namespace Volo.Abp.Cli.Commands; public class CreateMigrationAndRunMigratorCommand : IConsoleCommand, ITransientDependency { + public const string Name = "create-migration-and-run-migrator"; + public ICmdHelper CmdHelper { get; } public ILogger Logger { get; set; } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GetSourceCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GetSourceCommand.cs index 164fc52193..1cccf87f7b 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GetSourceCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GetSourceCommand.cs @@ -13,6 +13,8 @@ namespace Volo.Abp.Cli.Commands; public class GetSourceCommand : IConsoleCommand, ITransientDependency { + public const string Name = "get-source"; + private readonly SourceCodeDownloadService _sourceCodeDownloadService; public ModuleProjectBuilder ModuleProjectBuilder { get; } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/HelpCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/HelpCommand.cs index 317b1f34ea..918136c8d1 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/HelpCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/HelpCommand.cs @@ -12,6 +12,8 @@ namespace Volo.Abp.Cli.Commands; public class HelpCommand : IConsoleCommand, ITransientDependency { + public const string Name = "help"; + public ILogger Logger { get; set; } protected AbpCliOptions AbpCliOptions { get; } protected IServiceScopeFactory ServiceScopeFactory { get; } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/InstallLibsCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/InstallLibsCommand.cs index df95204f51..0cf2fd525b 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/InstallLibsCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/InstallLibsCommand.cs @@ -12,6 +12,8 @@ namespace Volo.Abp.Cli.Commands; public class InstallLibsCommand : IConsoleCommand, ITransientDependency { + public const string Name = "install-libs"; + public ILogger Logger { get; set; } protected IInstallLibsService InstallLibsService { get; } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ListModulesCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ListModulesCommand.cs index 1c59a37c46..a916598aac 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ListModulesCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ListModulesCommand.cs @@ -12,6 +12,8 @@ namespace Volo.Abp.Cli.Commands; public class ListModulesCommand : IConsoleCommand, ITransientDependency { + public const string Name = "list-modules"; + public ModuleInfoProvider ModuleInfoProvider { get; } public ILogger Logger { get; set; } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginCommand.cs index 9cd84a4d41..d98beb268d 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginCommand.cs @@ -18,6 +18,8 @@ namespace Volo.Abp.Cli.Commands; public class LoginCommand : IConsoleCommand, ITransientDependency { + public const string Name = "login"; + public ILogger Logger { get; set; } protected AuthService AuthService { get; } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginInfoCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginInfoCommand.cs index 19ce3853cd..0c456f0bca 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginInfoCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginInfoCommand.cs @@ -11,6 +11,8 @@ namespace Volo.Abp.Cli.Commands; public class LoginInfoCommand : IConsoleCommand, ITransientDependency { + public const string Name = "login-info"; + public ILogger Logger { get; set; } protected AuthService AuthService { get; } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LogoutCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LogoutCommand.cs index 6cefaad8e4..ed571dd7b4 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LogoutCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LogoutCommand.cs @@ -9,6 +9,8 @@ namespace Volo.Abp.Cli.Commands; public class LogoutCommand : IConsoleCommand, ITransientDependency { + public const string Name = "logout"; + public ILogger Logger { get; set; } protected AuthService AuthService { get; } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs index 6b01d93de9..2a8542d640 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs @@ -19,6 +19,8 @@ namespace Volo.Abp.Cli.Commands; public class NewCommand : ProjectCreationCommandBase, IConsoleCommand, ITransientDependency { + public const string Name = "new"; + public ILogger Logger { get; set; } protected TemplateProjectBuilder TemplateProjectBuilder { get; } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/PromptCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/PromptCommand.cs index f1ff0f6025..cbd9267989 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/PromptCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/PromptCommand.cs @@ -7,6 +7,8 @@ namespace Volo.Abp.Cli.Commands; public class PromptCommand : IConsoleCommand, ITransientDependency { + public const string Name = "prompt"; + public Task ExecuteAsync(CommandLineArgs commandLineArgs) { return Task.CompletedTask; diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/ConnectionStringProvider.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/ConnectionStringProvider.cs index cbb8ae6640..1a4d5bfc98 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/ConnectionStringProvider.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/ConnectionStringProvider.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using Volo.Abp.Cli.ProjectBuilding.Building; using Volo.Abp.DependencyInjection; @@ -21,7 +22,8 @@ public class ConnectionStringProvider : ITransientDependency case DatabaseManagementSystem.OracleDevart: return "Data Source=MyProjectName;Integrated Security=yes;"; case DatabaseManagementSystem.SQLite: - return $"Data Source={Path.Combine(outputFolder, "MyProjectName.db")};".Replace("\\", "\\\\"); + var comment = outputFolder.IsNullOrWhiteSpace() ? "//You need to change to an absolute filename" : string.Empty; + return $"Data Source={Path.Combine(outputFolder, "MyProjectName.db")};".Replace("\\", "\\\\") + comment; default: return null; } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SuiteCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SuiteCommand.cs index 760cbb0e45..0e797e682f 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SuiteCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SuiteCommand.cs @@ -15,6 +15,8 @@ namespace Volo.Abp.Cli.Commands; public class SuiteCommand : IConsoleCommand, ITransientDependency { + public const string Name = "suite"; + public ICmdHelper CmdHelper { get; } private readonly AbpNuGetIndexUrlService _nuGetIndexUrlService; private readonly NuGetService _nuGetService; diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToNightlyCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToNightlyCommand.cs index 0bfb3ea24b..4893680004 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToNightlyCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToNightlyCommand.cs @@ -8,6 +8,8 @@ namespace Volo.Abp.Cli.Commands; public class SwitchToNightlyCommand : IConsoleCommand, ITransientDependency { + public const string Name = "switch-to-nightly"; + private readonly PackagePreviewSwitcher _packagePreviewSwitcher; public SwitchToNightlyCommand(PackagePreviewSwitcher packagePreviewSwitcher) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToPreviewCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToPreviewCommand.cs index 2917a58449..84b58d6b8b 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToPreviewCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToPreviewCommand.cs @@ -8,6 +8,8 @@ namespace Volo.Abp.Cli.Commands; public class SwitchToPreviewCommand : IConsoleCommand, ITransientDependency { + public const string Name = "switch-to-preview"; + private readonly PackagePreviewSwitcher _packagePreviewSwitcher; public SwitchToPreviewCommand(PackagePreviewSwitcher packagePreviewSwitcher) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToStableCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToStableCommand.cs index 84400f85ef..cc0fa9ff75 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToStableCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToStableCommand.cs @@ -8,6 +8,8 @@ namespace Volo.Abp.Cli.Commands; public class SwitchToStableCommand : IConsoleCommand, ITransientDependency { + public const string Name = "switch-to-stable"; + private readonly PackagePreviewSwitcher _packagePreviewSwitcher; public SwitchToStableCommand(PackagePreviewSwitcher packagePreviewSwitcher) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/TranslateCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/TranslateCommand.cs index f4895538da..ea6b1d45f6 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/TranslateCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/TranslateCommand.cs @@ -15,6 +15,8 @@ namespace Volo.Abp.Cli.Commands; public class TranslateCommand : IConsoleCommand, ITransientDependency { + public const string Name = "translate"; + public ILogger Logger { get; set; } public Task ExecuteAsync(CommandLineArgs commandLineArgs) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/UpdateCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/UpdateCommand.cs index 7057df1dd0..fdc9b83fcf 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/UpdateCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/UpdateCommand.cs @@ -13,6 +13,8 @@ namespace Volo.Abp.Cli.Commands; public class UpdateCommand : IConsoleCommand, ITransientDependency { + public const string Name = "update"; + public ILogger Logger { get; set; } private readonly VoloNugetPackagesVersionUpdater _nugetPackagesVersionUpdater; diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo.Abp.EntityFrameworkCore.Oracle.Devart.csproj b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo.Abp.EntityFrameworkCore.Oracle.Devart.csproj index a3021984d2..918ac8ddf3 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo.Abp.EntityFrameworkCore.Oracle.Devart.csproj +++ b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo.Abp.EntityFrameworkCore.Oracle.Devart.csproj @@ -19,8 +19,8 @@ - - + + diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json index 5f73fa7ccd..449dd67480 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hant.json @@ -46,8 +46,8 @@ "GoHomePage": "返回首頁", "GoBack": "返回", "Search": "查詢", - "ItemWillBeDeletedMessageWithFormat": "{0} will be deleted!", - "ItemWillBeDeletedMessage": "This item will be deleted!", - "ManageYourAccount": "Manage your account" + "ItemWillBeDeletedMessageWithFormat": "{0} 將被刪除!", + "ItemWillBeDeletedMessage": "此項目將被刪除!", + "ManageYourAccount": "管理個人帳號" } } diff --git a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs index b25eb8023d..fbaf6b4d3e 100644 --- a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs +++ b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs @@ -59,10 +59,10 @@ public class PersonAppServiceClientProxy_Tests : AbpHttpClientTestBase var id2 = Guid.NewGuid(); var @params = await _peopleAppService.GetParams(new List - { - id1, - id2 - }, new[] { "name1", "name2" }); + { + id1, + id2 + }, new[] { "name1", "name2" }); @params.ShouldContain(id1.ToString("N")); @params.ShouldContain(id2.ToString("N")); @@ -224,10 +224,10 @@ public class PersonAppServiceClientProxy_Tests : AbpHttpClientTestBase memoryStream2.Position = 0; var result = await _peopleAppService.UploadMultipleAsync(new List() - { - new RemoteStreamContent(memoryStream, "File1.rtf", "application/rtf"), - new RemoteStreamContent(memoryStream2, "File2.rtf", "application/rtf2") - }); + { + new RemoteStreamContent(memoryStream, "File1.rtf", "application/rtf"), + new RemoteStreamContent(memoryStream2, "File2.rtf", "application/rtf2") + }); result.ShouldBe("File1:application/rtf:File1.rtfFile2:application/rtf2:File2.rtf"); } @@ -263,11 +263,11 @@ public class PersonAppServiceClientProxy_Tests : AbpHttpClientTestBase var result = await _peopleAppService.CreateMultipleFileAsync(new CreateMultipleFileInput() { Name = "123.rtf", - Contents = new List() - { - new RemoteStreamContent(memoryStream, "1-1.rtf", "application/rtf"), - new RemoteStreamContent(memoryStream2, "1-2.rtf", "application/rtf2") - }, + Contents = new List() + { + new RemoteStreamContent(memoryStream, "1-1.rtf", "application/rtf"), + new RemoteStreamContent(memoryStream2, "1-2.rtf", "application/rtf2") + }, Inner = new CreateFileInput() { Name = "789.rtf", @@ -283,18 +283,18 @@ public class PersonAppServiceClientProxy_Tests : AbpHttpClientTestBase var result = await _peopleAppService.GetParamsFromQueryAsync(new GetParamsInput() { NameValues = new List() + { + new GetParamsNameValue() { - new GetParamsNameValue() - { - Name = "name1", - Value = "value1" - }, - new GetParamsNameValue() - { - Name = "name2", - Value = "value2" - } + Name = "name1", + Value = "value1" }, + new GetParamsNameValue() + { + Name = "name2", + Value = "value2" + } + }, NameValue = new GetParamsNameValue() { Name = "name3", @@ -310,18 +310,18 @@ public class PersonAppServiceClientProxy_Tests : AbpHttpClientTestBase var result = await _peopleAppService.GetParamsFromFormAsync(new GetParamsInput() { NameValues = new List() + { + new GetParamsNameValue() { - new GetParamsNameValue() - { - Name = "name1", - Value = "value1" - }, - new GetParamsNameValue() - { - Name = "name2", - Value = "value2" - } + Name = "name1", + Value = "value1" }, + new GetParamsNameValue() + { + Name = "name2", + Value = "value2" + } + }, NameValue = new GetParamsNameValue() { Name = "name3", diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/CreateFileInput.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/CreateFileInput.cs index 9c81620bdd..d11809d810 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/CreateFileInput.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/CreateFileInput.cs @@ -6,5 +6,5 @@ public class CreateFileInput { public string Name { get; set; } - public RemoteStreamContent Content { get; set; } + public IRemoteStreamContent Content { get; set; } } diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/CreateMultipleFileInput.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/CreateMultipleFileInput.cs index ca55eb30ec..99cbefc6cf 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/CreateMultipleFileInput.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/CreateMultipleFileInput.cs @@ -7,7 +7,7 @@ public class CreateMultipleFileInput { public string Name { get; set; } - public IEnumerable Contents { get; set; } + public IEnumerable Contents { get; set; } public CreateFileInput Inner { get; set; } } diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/zh-Hant.json b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/zh-Hant.json index 1d87b80110..49584e5af0 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/zh-Hant.json +++ b/modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/zh-Hant.json @@ -1,7 +1,7 @@ { "culture": "zh-Hant", "texts": { - "AddSubMenuItem": "添加子菜單項", + "AddSubMenuItem": "添加子選單項目", "AreYouSure": "你確定嗎?", "BlogDeletionConfirmationMessage": "部落格 '{0}' 將被刪除. 你確定嗎?", "BlogFeatureNotAvailable": "目前此功能不可使用. 請用 `GlobalFeatureManager` 來啟用它.", @@ -47,15 +47,15 @@ "ExportCSV": "匯出 CSV", "Features": "功能", "GenericDeletionConfirmationMessage": "你確定刪除 '{0}' 嗎?", - "IsActive": "積極的", + "IsActive": "是否啟用", "LastModification": "最後一次修改", "LastModificationTime": "最後修改時間", "LoginToAddComment": "登錄後添加評論", "LoginToRate": "登錄後進行評分", "LoginToReact": "登錄以作出反應", "LoginToReply": "登錄後進行回覆", - "MainMenu": "主菜單", - "MakeMainMenu": "製作主菜單", + "MainMenu": "主選單", + "MakeMainMenu": "製作主選單", "Menu:CMS": "CMS", "Menus": "菜單", "MenuDeletionConfirmationMessage": "菜單“{0}”將被刪除。你確定嗎?", @@ -64,10 +64,10 @@ "MenuItems": "菜單項", "Message": "消息", "MessageDeletionConfirmationMessage": "這條評論將被完全刪除", - "NewBlog": "新博客", - "NewBlogPost": "新博文", - "NewMenu": "新菜單", - "NewMenuItem": "新的根菜單項", + "NewBlog": "新部落格", + "NewBlogPost": "新部落格貼文", + "NewMenu": "新選單", + "NewMenuItem": "新的根選單", "NewPage": "新的一頁", "NewTag": "新標籤", "NoMenuItems": "還沒有菜單項!", @@ -104,9 +104,9 @@ "Permission:MenuManagement.Delete": "刪除", "Permission:MenuManagement.Update": "更新", "Permission:Menus": "Menu Management", - "Permission:Menus.Create": "Create", - "Permission:Menus.Delete": "Delete", - "Permission:Menus.Update": "Update", + "Permission:Menus.Create": "創建", + "Permission:Menus.Delete": "刪除", + "Permission:Menus.Update": "更新", "Permission:PageManagement": "頁面管理", "Permission:PageManagement:Create": "創建", "Permission:PageManagement:Delete": "刪除", @@ -150,7 +150,7 @@ "Update": "更新", "UpdatePreferenceSuccessMessage": "您的 preferences 已經保存", "UpdateYourEmailPreferences": "更新你的郵件preferences", - "UnMakeMainMenu": "取消主菜單", + "UnMakeMainMenu": "取消主選單", "UploadFailedMessage": "上傳失敗", "UserId": "用戶Id", "Username": "用戶名稱", @@ -158,6 +158,7 @@ "YourEmailAddress": "你的郵件地址", "YourFullName": "你的全名", "YourMessage": "你的消息", - "YourReply": "你的回覆" + "YourReply": "你的回覆", + "MarkdownSupported": "支援 Markdown ." } -} \ No newline at end of file +} diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpClaimsService.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpClaimsService.cs index 57ee725046..c3caa48971 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpClaimsService.cs +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpClaimsService.cs @@ -5,6 +5,7 @@ using IdentityModel; using IdentityServer4.Services; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.JsonWebTokens; using Volo.Abp.Security.Claims; namespace Volo.Abp.IdentityServer; @@ -20,6 +21,7 @@ public class AbpClaimsService : DefaultClaimsService AbpClaimTypes.ImpersonatorUserId, AbpClaimTypes.Name, AbpClaimTypes.SurName, + JwtRegisteredClaimNames.UniqueName, JwtClaimTypes.PreferredUserName, JwtClaimTypes.GivenName, JwtClaimTypes.FamilyName, diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpUserClaimsFactory.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpUserClaimsFactory.cs index fd62e61c3e..18ba6cbe2b 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpUserClaimsFactory.cs +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpUserClaimsFactory.cs @@ -1,10 +1,11 @@ -using System; +using System; using System.Linq; using System.Security.Claims; using System.Security.Principal; using System.Threading.Tasks; using IdentityModel; using Microsoft.AspNetCore.Identity; +using Microsoft.IdentityModel.JsonWebTokens; using Volo.Abp.DependencyInjection; using IdentityUser = Volo.Abp.Identity.IdentityUser; @@ -41,6 +42,10 @@ public class AbpUserClaimsFactory : IUserClaimsPrincipalFactory { identity.RemoveClaim(usernameClaim); identity.AddIfNotContains(new Claim(JwtClaimTypes.PreferredUserName, username)); + + //https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/1627 + //https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/05e02b5e0383be40e45c667c12f6667d38e33fcc/src/System.IdentityModel.Tokens.Jwt/ClaimTypeMapping.cs#L52 + identity.AddIfNotContains(new Claim(JwtRegisteredClaimNames.UniqueName, username)); } if (!identity.HasClaim(x => x.Type == JwtClaimTypes.Name)) diff --git a/nupkg/common.ps1 b/nupkg/common.ps1 index e173a913c2..fbcbae15dd 100644 --- a/nupkg/common.ps1 +++ b/nupkg/common.ps1 @@ -136,7 +136,7 @@ $projects = ( "framework/src/Volo.Abp.EntityFrameworkCore", "framework/src/Volo.Abp.EntityFrameworkCore.MySQL", "framework/src/Volo.Abp.EntityFrameworkCore.Oracle", - # "framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart", + "framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart", "framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql", "framework/src/Volo.Abp.EntityFrameworkCore.Sqlite", "framework/src/Volo.Abp.EntityFrameworkCore.SqlServer", diff --git a/test/DistEvents/DistDemoApp.Shared/DistDemoApp.Shared.csproj b/test/DistEvents/DistDemoApp.Shared/DistDemoApp.Shared.csproj index 4f5cb8ad84..f43d82dd19 100644 --- a/test/DistEvents/DistDemoApp.Shared/DistDemoApp.Shared.csproj +++ b/test/DistEvents/DistDemoApp.Shared/DistDemoApp.Shared.csproj @@ -17,7 +17,7 @@ - + diff --git a/test/DistEvents/DistDemoApp.Shared/DistDemoAppSharedModule.cs b/test/DistEvents/DistDemoApp.Shared/DistDemoAppSharedModule.cs index 138b64f567..936264e828 100644 --- a/test/DistEvents/DistDemoApp.Shared/DistDemoAppSharedModule.cs +++ b/test/DistEvents/DistDemoApp.Shared/DistDemoAppSharedModule.cs @@ -5,7 +5,7 @@ using StackExchange.Redis; using Volo.Abp.Autofac; using Volo.Abp.Domain; using Volo.Abp.Domain.Entities.Events.Distributed; -using Volo.Abp.EventBus.Boxes; +using Volo.Abp.EventBus; using Volo.Abp.Modularity; namespace DistDemoApp @@ -13,8 +13,8 @@ namespace DistDemoApp [DependsOn( typeof(AbpAutofacModule), typeof(AbpDddDomainModule), - typeof(AbpEventBusBoxesModule) - )] + typeof(AbpEventBusModule) + )] public class DistDemoAppSharedModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) @@ -28,7 +28,7 @@ namespace DistDemoApp options.EtoMappings.Add(); options.AutoEventSelectors.Add(); }); - + context.Services.AddSingleton(sp => { var connection = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]); @@ -36,4 +36,4 @@ namespace DistDemoApp }); } } -} \ No newline at end of file +}