From 340e163b498ec14b137e73dd28eacd95a7bd1861 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Wed, 13 May 2020 00:15:25 +0800 Subject: [PATCH] Update document. --- ...Application-Modules-Overriding-Services.md | 19 +- .../Getting-Started-AspNetCore-Application.md | 15 +- ...ure-Active-Directory-Authentication-MVC.md | 24 +- docs/zh-Hans/Samples/Index.md | 56 +++ .../Tutorials/AspNetCore-Mvc/Part-II.md | 434 +----------------- docs/zh-Hans/docs-nav.json | 4 + 6 files changed, 104 insertions(+), 448 deletions(-) create mode 100644 docs/zh-Hans/Samples/Index.md diff --git a/docs/zh-Hans/Customizing-Application-Modules-Overriding-Services.md b/docs/zh-Hans/Customizing-Application-Modules-Overriding-Services.md index caa448a14a..94a2120059 100644 --- a/docs/zh-Hans/Customizing-Application-Modules-Overriding-Services.md +++ b/docs/zh-Hans/Customizing-Application-Modules-Overriding-Services.md @@ -107,22 +107,25 @@ public class MyIdentityUserAppService : IdentityUserAppService public class MyIdentityUserManager : IdentityUserManager { public MyIdentityUserManager( - IdentityUserStore store, + IdentityUserStore store, + IIdentityRoleRepository roleRepository, + IIdentityUserRepository userRepository, IOptions optionsAccessor, IPasswordHasher passwordHasher, IEnumerable> userValidators, IEnumerable> passwordValidators, - ILookupNormalizer keyNormalizer, - IdentityErrorDescriber errors, - IServiceProvider services, + ILookupNormalizer keyNormalizer, + IdentityErrorDescriber errors, + IServiceProvider services, ILogger logger, - ICancellationTokenProvider cancellationTokenProvider - ) : base( - store, + ICancellationTokenProvider cancellationTokenProvider) : + base(store, + roleRepository, + userRepository, optionsAccessor, passwordHasher, userValidators, - passwordValidators, + passwordValidators, keyNormalizer, errors, services, diff --git a/docs/zh-Hans/Getting-Started-AspNetCore-Application.md b/docs/zh-Hans/Getting-Started-AspNetCore-Application.md index 9d195562fd..0334609bd3 100644 --- a/docs/zh-Hans/Getting-Started-AspNetCore-Application.md +++ b/docs/zh-Hans/Getting-Started-AspNetCore-Application.md @@ -30,10 +30,8 @@ ABP是一个模块化框架,它需要一个**启动 (根) 模块**继承自``Abp ````C# using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Volo.Abp; -using Volo.Abp.AspNetCore.Modularity; using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.Modularity; @@ -42,7 +40,8 @@ namespace BasicAspNetCoreApplication [DependsOn(typeof(AbpAspNetCoreMvcModule))] public class AppModule : AbpModule { - public override void OnApplicationInitialization(ApplicationInitializationContext context) + public override void OnApplicationInitialization( + ApplicationInitializationContext context) { var app = context.GetApplicationBuilder(); var env = context.GetEnvironment(); @@ -51,8 +50,14 @@ namespace BasicAspNetCoreApplication { app.UseDeveloperExceptionPage(); } + else + { + app.UseExceptionHandler("/Error"); + } - app.UseMvcWithDefaultRoute(); + app.UseStaticFiles(); + app.UseRouting(); + app.UseConfiguredEndpoints(); } } } diff --git a/docs/zh-Hans/How-To/Azure-Active-Directory-Authentication-MVC.md b/docs/zh-Hans/How-To/Azure-Active-Directory-Authentication-MVC.md index 8b282bbe56..34aab9fa46 100644 --- a/docs/zh-Hans/How-To/Azure-Active-Directory-Authentication-MVC.md +++ b/docs/zh-Hans/How-To/Azure-Active-Directory-Authentication-MVC.md @@ -157,6 +157,16 @@ private void ConfigureAuthentication(ServiceConfigurationContext context, IConfi JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier); ```` +* Help! 我一直得到 ***System.ArgumentNullException: Value cannot be null. (Parameter 'userName')*** 错误! + + * 当你使用 Azure Authority **v2.0 端点** 而不请求 `email` 域, 会发生这些情况. [Abp 创建用户检查了唯一的邮箱](https://github.com/abpframework/abp/blob/037ef9abe024c03c1f89ab6c933710bcfe3f5c93/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs#L208). 只需添加 + + ````csharp + options.Scope.Add("email"); + ```` + + 到你的 openid 配置. + * Help! 我一直得到 ***AADSTS50011: The reply URL specified in the request does not match the reply URLs configured for the application*** 错误! * 如果你在appsettings设置 **CallbackPath** 为: @@ -170,15 +180,17 @@ private void ConfigureAuthentication(ServiceConfigurationContext context, IConfi 你在azure门户的应用程序**重定向URI**必须具有之类 `https://localhost:44320/signin-azuread-oidc` 的, 而不仅是 `/signin-azuread-oidc`. -* Help! 我一直得到 ***System.ArgumentNullException: Value cannot be null. (Parameter 'userName')*** 错误! +* Help! 我一直得到 ***AADSTS700051: The response_type 'token' is not enabled for the application.*** 错误! - * 当你使用 Azure Authority **v2.0 端点** 而不请求 `email` 域, 会发生这些情况. [Abp 创建用户检查了唯一的邮箱](https://github.com/abpframework/abp/blob/037ef9abe024c03c1f89ab6c933710bcfe3f5c93/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs#L208). 只需添加 + * 当你请求**token**(访问令牌)和**id_token**时没有在Azure门户应用程序启用访问令牌时会发生这个错误,只需勾选ID令牌顶部的**访问令牌**复选框即可同时请求令牌. - ````csharp - options.Scope.Add("email"); - ```` +* Help! 我一直得到 ***AADSTS7000218: The request body must contain the following parameter: 'client_assertion' or 'client_secret*** 错误! - 到你的 openid 配置. + * 当你与 **id_token** 一起请求 **code**时,你需要在Azure门户的**证书和机密**菜单下添加**证书和机密**. 然后你需要添加openid配置选项: + + ````csharp + options.ClientSecret = "Value of your secret on azure portal"; + ```` * 如何**调试/监视**在映射之前获得的声明? diff --git a/docs/zh-Hans/Samples/Index.md b/docs/zh-Hans/Samples/Index.md new file mode 100644 index 0000000000..774d6467ef --- /dev/null +++ b/docs/zh-Hans/Samples/Index.md @@ -0,0 +1,56 @@ +# 示例应用 + +这些是ABP框架创建的官方示例. 这些示例大部分在[abpframework/abp-samples](https://github.com/abpframework/abp-samples) GitHub 仓库. + +### 微服务示例 + +演示如何基于微服务体系结构构建系统的完整解决方案. + +* [示例的文档](Microservice-Demo.md) +* [源码](https://github.com/abpframework/abp/tree/dev/samples/MicroserviceDemo) +* [微服务架构文档](../Microservice-Architecture.md) + +### Book Store + +一个简单的CRUD应用程序,展示了使用ABP框架开发应用程序的基本原理. 使用不同的技术实现了相同的示例: + +* **Book Store: Razor Pages UI & Entity Framework Core** + + * [教程](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=MVC) + * [源码](https://github.com/abpframework/abp-samples/tree/master/BookStore) + +* **Book Store: Angular UI & MongoDB** + + * [教程](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=NG) + * [源码](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) + +* **Book Store: Modular application (Razor Pages UI & EF Core)** + + * [源码](https://github.com/abpframework/abp-samples/tree/master/BookStore-Modular) + +如果没有Razor Pages & MongoDB 结合,但你可以检查两个文档来理解它,因为DB和UI不会互相影响. + +### 其他示例 + +* **Entity Framework 迁移**: 演示如何将应用程序拆分为多个数据库的解决方案. 每个数据库包含不同的模块. + * [源码](https://github.com/abpframework/abp-samples/tree/master/DashboardDemo) + * [EF Core数据库迁移文档](../Entity-Framework-Core-Migrations.md) +* **Dashboard Demo**: 一个简单的应用程序,展示了如何在ASP.NET Core MVC UI中使用widget系统. + * [源码](https://github.com/abpframework/abp-samples/tree/master/DashboardDemo) + * [Widget 文档](../UI/AspNetCore/Widgets.md) +* **RabbitMQ 事件总线 Demo**: 由两个通过RabbitMQ集成的分布式事件相互通信的应用程序组成的解决方案. + * [源码](https://github.com/abpframework/abp-samples/tree/master/RabbitMqEventBus) + * [分布式事件总线文档](../Distributed-Event-Bus.md) + * [RabbitMQ 分布式事件总线集成文档](../Distributed-Event-Bus-RabbitMQ-Integration.md) +* **自定义认证**: 如何为ASP.NET Core MVC / Razor Pages应用程序自定义身份验证的解决方案. + * [源码](https://github.com/abpframework/abp-samples/tree/master/aspnet-core/Authentication-Customization) + * 相关 "[How To](../How-To/Index.md)" 文档: + * [Azure Active Directory 认证](../How-To/Azure-Active-Directory-Authentication-MVC.md) + * [自定义登录页面](../How-To/Customize-Login-Page-MVC.md) + * [自定义 SignIn Manager](../How-To/Customize-SignIn-Manager.md) +* **空的ASP.NET Core应用程序**: 从基本的ASP.NET Core应用程序使用ABP框架. + * [源码](https://github.com/abpframework/abp-samples/tree/master/BasicAspNetCoreApplication) + * [文档](../Getting-Started-AspNetCore-Application.md) +* **空的控制台应用程序**: 从基本的控制台应用程序安装ABP框架. + * [源码](https://github.com/abpframework/abp-samples/tree/master/BasicConsoleApplication) + * [文档](../Getting-Started-Console-Application.md) \ No newline at end of file diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-II.md b/docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-II.md index 2d4b344afa..e981259db8 100644 --- a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-II.md +++ b/docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-II.md @@ -1,432 +1,8 @@ -## ASP.NET Core MVC 教程 - 第二章 +# 教程 -### 关于本教程 +## 应用程序开发 -这是ASP.NET Core MVC教程系列的第二章. 查看其它章节 +* [ASP.NET Core MVC / Razor Pages UI](../Part-1?UI=MVC) +* [Angular UI](../Part-1?UI=NG) -* [Part I: 创建项目和书籍列表页面](Part-I.md) -* **Part II: 创建,编辑,删除书籍(本章)** -* [Part III: 集成测试](Part-III.md) - -你可以从[GitHub存储库](https://github.com/volosoft/abp/tree/master/samples/BookStore)访问应用程序的**源代码**. - -> 你也可以观看由ABP社区成员为本教程录制的[视频课程](https://amazingsolutions.teachable.com/p/lets-build-the-bookstore-application). - -### 新增 Book 实体 - -通过本节, 你将会了解如何创建一个 modal form 来实现新增书籍的功能. 最终成果如下图所示: - -![bookstore-create-dialog](images/bookstore-create-dialog-2.png) - -#### 新建 modal form - -在 `Acme.BookStore.Web` 项目的 `Pages/Books` 目录下新建一个 `CreateModal.cshtml` Razor页面: - -![bookstore-add-create-dialog](images/bookstore-add-create-dialog-v2.png) - -##### CreateModal.cshtml.cs - -展开 `CreateModal.cshtml`,打开 `CreateModal.cshtml.cs` 代码文件,用如下代码替换 `CreateModalModel` 类的实现: - -````C# -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; - -namespace Acme.BookStore.Web.Pages.Books -{ - public class CreateModalModel : BookStorePageModel - { - [BindProperty] - public CreateUpdateBookDto Book { get; set; } - - private readonly IBookAppService _bookAppService; - - public CreateModalModel(IBookAppService bookAppService) - { - _bookAppService = bookAppService; - } - - public async Task OnPostAsync() - { - await _bookAppService.CreateAsync(Book); - return NoContent(); - } - } -} -```` - -* 该类派生于 `BookStorePageModel` 而非默认的 `PageModel`. `BookStorePageModel` 继承了 `PageModel` 并且添加了一些可以被你的page model类使用的通用属性和方法. -* `Book` 属性上的 `[BindProperty]` 特性将post请求提交上来的数据绑定到该属性上. -* 该类通过构造函数注入了 `IBookAppService` 应用服务,并且在 `OnPostAsync` 处理程序中调用了服务的 `CreateAsync` 方法. - -##### CreateModal.cshtml - -打开 `CreateModal.cshtml` 文件并粘贴如下代码: - -````html -@page -@inherits Acme.BookStore.Web.Pages.BookStorePage -@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal -@model Acme.BookStore.Web.Pages.Books.CreateModalModel -@{ - Layout = null; -} - - - - - - - - - -```` - -* 这个 modal 使用 `abp-dynamic-form` Tag Helper 根据 `CreateBookViewModel` 类自动构建了表单. - * `abp-model` 指定了 `Book` 属性为模型对象. - * `data-ajaxForm` 设置了表单通过AJAX提交,而不是经典的页面回发. - * `abp-form-content` tag helper 作为表单控件渲染位置的占位符 (这是可选的,只有你在 `abp-dynamic-form` 中像本示例这样添加了其他内容才需要). - -#### 添加 "New book" 按钮 - -打开 `Pages/Books/Index.cshtml` 并按如下代码修改 `abp-card-header` : - -````html - - - -

@L["Books"]

-
- - - -
-
-```` - -如下图所示,只是在表格 **右上方** 添加了 **New book** 按钮: - -![bookstore-new-book-button](images/bookstore-new-book-button.png) - -打开 `Pages/books/index.js` 在datatable配置代码后面添加如下代码: - -````js -var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal'); - -createModal.onResult(function () { - dataTable.ajax.reload(); -}); - -$('#NewBookButton').click(function (e) { - e.preventDefault(); - createModal.open(); -}); -```` - -* `abp.ModalManager` 是一个在客户端打开和管理modal的辅助类.它基于Twitter Bootstrap的标准modal组件通过简化的API抽象隐藏了许多细节. - -现在,你可以 **运行程序** 通过新的 modal form 来创建书籍了. - -### 编辑更新已存在的 Book 实体 - -在 `Acme.BookStore.Web` 项目的 `Pages/Books` 目录下新建一个名叫 `EditModal.cshtml` 的Razor页面: - -![bookstore-add-edit-dialog](images/bookstore-add-edit-dialog.png) - -#### EditModal.cshtml.cs - -展开 `EditModal.cshtml`,打开 `EditModal.cshtml.cs` 文件( `EditModalModel` 类) 并替换成以下代码: - -````csharp -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; - -namespace Acme.BookStore.Web.Pages.Books -{ - public class EditModalModel : BookStorePageModel - { - [HiddenInput] - [BindProperty(SupportsGet = true)] - public Guid Id { get; set; } - - [BindProperty] - public CreateUpdateBookDto Book { get; set; } - - private readonly IBookAppService _bookAppService; - - public EditModalModel(IBookAppService bookAppService) - { - _bookAppService = bookAppService; - } - - public async Task OnGetAsync() - { - var bookDto = await _bookAppService.GetAsync(Id); - Book = ObjectMapper.Map(bookDto); - } - - public async Task OnPostAsync() - { - await _bookAppService.UpdateAsync(Id, Book); - return NoContent(); - } - } -} -```` - -* `[HiddenInput]` 和 `[BindProperty]` 是标准的 ASP.NET Core MVC 特性.这里启用 `SupportsGet` 从Http请求的查询字符串中获取Id的值. -* 在 `OnGetAsync` 方法中,将 `BookAppService.GetAsync` 方法返回的 `BookDto` 映射成 `CreateUpdateBookDto` 并赋值给Book属性. -* `OnPostAsync` 方法直接使用 `BookAppService.UpdateAsync` 来更新实体. - -#### BookDto到CreateUpdateBookDto对象映射 - -为了执行`BookDto`到`CreateUpdateBookDto`对象映射,请打开`Acme.BookStore.Web`项目中的`BookStoreWebAutoMapperProfile.cs`并更改它,如下所示: - -````csharp -using AutoMapper; - -namespace Acme.BookStore.Web -{ - public class BookStoreWebAutoMapperProfile : Profile - { - public BookStoreWebAutoMapperProfile() - { - CreateMap(); - } - } -} -```` - -* 刚刚添加了`CreateMap();`作为映射定义. - -#### EditModal.cshtml - -将 `EditModal.cshtml` 页面内容替换成如下代码: - -````html -@page -@inherits Acme.BookStore.Web.Pages.BookStorePage -@using Acme.BookStore.Web.Pages.Books -@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal -@model EditModalModel -@{ - Layout = null; -} - - - - - - - - - - -```` - -这个页面内容和 `CreateModal.cshtml` 非常相似,除了以下几点: - -* 它包含`id`属性的`abp-input`, 用于存储编辑书的id(它是隐藏的Input) -* 此页面指定的post地址是`Books/EditModal`, 并用文本 *Update* 作为 modal 标题. - -#### 为表格添加 "操作(Actions)" 下拉菜单 - -我们将为表格每行添加下拉按钮 ("Actions") . 最终效果如下: - -![bookstore-books-table-actions](images/bookstore-books-table-actions.png) - -打开 `Pages/Books/Index.cshtml` 页面,并按下方所示修改表格部分的代码: - -````html - - - - @L["Actions"] - @L["Name"] - @L["Type"] - @L["PublishDate"] - @L["Price"] - @L["CreationTime"] - - - -```` - -* 只是为"Actions"增加了一个 `th` 标签. - -打开 `Pages/books/index.js` 并用以下内容进行替换: - -````js -$(function () { - - var l = abp.localization.getResource('BookStore'); - - var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal'); - var editModal = new abp.ModalManager(abp.appPath + 'Books/EditModal'); - - var dataTable = $('#BooksTable').DataTable(abp.libs.datatables.normalizeConfiguration({ - processing: true, - serverSide: true, - paging: true, - searching: false, - autoWidth: false, - scrollCollapse: true, - order: [[1, "asc"]], - ajax: abp.libs.datatables.createAjax(acme.bookStore.book.getList), - columnDefs: [ - { - rowAction: { - items: - [ - { - text: l('Edit'), - action: function (data) { - editModal.open({ id: data.record.id }); - } - } - ] - } - }, - { data: "name" }, - { data: "type" }, - { data: "publishDate" }, - { data: "price" }, - { data: "creationTime" } - ] - })); - - createModal.onResult(function () { - dataTable.ajax.reload(); - }); - - editModal.onResult(function () { - dataTable.ajax.reload(); - }); - - $('#NewBookButton').click(function (e) { - e.preventDefault(); - createModal.open(); - }); -}); -```` - -* 通过 `abp.localization.getResource('BookStore')` 可以在客户端使用服务器端定义的相同的本地化语言文本. -* 添加了一个名为 `createModal` 的新的 `ModalManager` 来打开创建用的 modal 对话框. -* 添加了一个名为 `editModal` 的新的 `ModalManager` 来打开编辑用的 modal 对话框. -* 在 `columnDefs` 起始处新增一列用于显示 "Actions" 下拉按钮. -* "New Book"动作只需调用`createModal.open`来打开创建对话框. -* "Edit" 操作只是简单调用 `editModal.open` 来打开编辑对话框. - -现在,你可以运行程序,通过编辑操作来更新任一个book实体. - -### 删除一个已有的Book实体 - -打开 `Pages/books/index.js` 文件,在 `rowAction` `items` 下新增一项: - -````js -{ - text: l('Delete'), - confirmMessage: function (data) { - return l('BookDeletionConfirmationMessage', data.record.name); - }, - action: function (data) { - acme.bookStore.book - .delete(data.record.id) - .then(function() { - abp.notify.info(l('SuccessfullyDeleted')); - dataTable.ajax.reload(); - }); - } -} -```` - -* `confirmMessage` 用来在实际执行 `action` 之前向用户进行确认. -* 通过javascript代理方法 `acme.bookStore.book.delete` 执行一个AJAX请求来删除一个book实体. -* `abp.notify.info` 用来在执行删除操作后显示一个toastr通知信息. - -最终的 `index.js` 文件内容如下所示: - -````js -$(function () { - - var l = abp.localization.getResource('BookStore'); - - var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal'); - var editModal = new abp.ModalManager(abp.appPath + 'Books/EditModal'); - - var dataTable = $('#BooksTable').DataTable(abp.libs.datatables.normalizeConfiguration({ - processing: true, - serverSide: true, - paging: true, - searching: false, - autoWidth: false, - scrollCollapse: true, - order: [[1, "asc"]], - ajax: abp.libs.datatables.createAjax(acme.bookStore.book.getList), - columnDefs: [ - { - rowAction: { - items: - [ - { - text: l('Edit'), - action: function (data) { - editModal.open({ id: data.record.id }); - } - }, - { - text: l('Delete'), - confirmMessage: function (data) { - return l('BookDeletionConfirmationMessage', data.record.name); - }, - action: function (data) { - acme.bookStore.book - .delete(data.record.id) - .then(function() { - abp.notify.info(l('SuccessfullyDeleted')); - dataTable.ajax.reload(); - }); - } - } - ] - } - }, - { data: "name" }, - { data: "type" }, - { data: "publishDate" }, - { data: "price" }, - { data: "creationTime" } - ] - })); - - createModal.onResult(function () { - dataTable.ajax.reload(); - }); - - editModal.onResult(function () { - dataTable.ajax.reload(); - }); - - $('#NewBookButton').click(function (e) { - e.preventDefault(); - createModal.open(); - }); -}); -```` - -打开`Acme.BookStore.Domain.Shared`项目中的`en.json`并添加以下行: - -````json -"BookDeletionConfirmationMessage": "Are you sure to delete the book {0}?", -"SuccessfullyDeleted": "Successfully deleted" -```` - -运行程序并尝试删除一个book实体. - -### 下一章 - -查看本教程的 [下一章](Part-III.md) . + \ No newline at end of file diff --git a/docs/zh-Hans/docs-nav.json b/docs/zh-Hans/docs-nav.json index 9f7d69a784..d61aa63ce6 100644 --- a/docs/zh-Hans/docs-nav.json +++ b/docs/zh-Hans/docs-nav.json @@ -495,6 +495,10 @@ { "text": "示例", "items": [ + { + "text": "所有示例", + "path": "Samples/Index.md" + }, { "text": "微服务示例", "path": "Samples/Microservice-Demo.md"