Abp vnext Web应用程序开发教程 9 —— 作者:用户界面

关于本教程

本教程基于版本3.1

在本教程系列中,您将构建一个名为Acme.BookStore的基于ABPWeb应用程序。该应用程序用于管理书籍及其作者的列表。它是使用以下技术开发的:

  • 实体框架核心作为ORM提供者。
  • MVC/Razor页面作为UI框架。

本教程分为以下部分:

第1部分:创建服务器端

第2部分:书籍列表页面

第3部分:创建、更新和删除书籍

第4部分:集成测试

第5部分:授权

第6部分:作者:领域层

第7部分:作者:数据库集成

第8部分:作者:应用程序层

第9部分:作者:用户界面(此部分)

第10部分:书与作者的关系

下载源代码

MVC (Razor Pages) UI with EF Core

介绍

本部分说明了如何为前面部分介绍的Author实体创建CRUD页面。

图书列表页面

Acme.BookStore.Web项目Pages/Authors文件夹下创建一个新的Index.cshtml razor页面,并按如下所示更改内容。

Index.cshtml

@page
@using Acme.BookStore.Localization
@using Acme.BookStore.Permissions
@using Acme.BookStore.Web.Pages.Authors
@using Microsoft.AspNetCore.Authorization
@using Microsoft.Extensions.Localization
@inject IStringLocalizer<BookStoreResource> L
@inject IAuthorizationService AuthorizationService
@model IndexModel

@section scripts
{
    <abp-script src="/Pages/Authors/Index.js"/>
}

<abp-card>
    <abp-card-header>
        <abp-row>
            <abp-column size-md="_6">
                <abp-card-title>@L["Authors"]</abp-card-title>
            </abp-column>
            <abp-column size-md="_6" class="text-right">
                @if (await AuthorizationService
                    .IsGrantedAsync(BookStorePermissions.Authors.Create))
                {
                    <abp-button id="NewAuthorButton"
                                text="@L["NewAuthor"].Value"
                                icon="plus"
                                button-type="Primary"/>
                }
            </abp-column>
        </abp-row>
    </abp-card-header>
    <abp-card-body>
        <abp-table striped-rows="true" id="AuthorsTable"></abp-table>
    </abp-card-body>
</abp-card>

这是一个简单的页面,类似于我们之前创建的“图书(Books)”页面。它将导入一个JavaScript文件,下面将对其进行介绍。

IndexModel.cshtml.cs

using Microsoft.AspNetCore.Mvc.RazorPages;

namespace Acme.BookStore.Web.Pages.Authors
{
    public class IndexModel : PageModel
    {
        public void OnGet()
        {

        }
    }
}

Index.js

$(function () {
    var l = abp.localization.getResource('BookStore');
    var createModal = new abp.ModalManager(abp.appPath + 'Authors/CreateModal');
    var editModal = new abp.ModalManager(abp.appPath + 'Authors/EditModal');

    var dataTable = $('#AuthorsTable').DataTable(
        abp.libs.datatables.normalizeConfiguration({
            serverSide: true,
            paging: true,
            order: [[1, "asc"]],
            searching: false,
            scrollX: true,
            ajax: abp.libs.datatables.createAjax(acme.bookStore.authors.author.getList),
            columnDefs: [
                {
                    title: l('Actions'),
                    rowAction: {
                        items:
                            [
                                {
                                    text: l('Edit'),
                                    visible: 
                                        abp.auth.isGranted('BookStore.Authors.Edit'),
                                    action: function (data) {
                                        editModal.open({ id: data.record.id });
                                    }
                                },
                                {
                                    text: l('Delete'),
                                    visible: 
                                        abp.auth.isGranted('BookStore.Authors.Delete'),
                                    confirmMessage: function (data) {
                                        return l(
                                            'AuthorDeletionConfirmationMessage',
                                            data.record.name
                                        );
                                    },
                                    action: function (data) {
                                        acme.bookStore.authors.author
                                            .delete(data.record.id)
                                            .then(function() {
                                                abp.notify.info(
                                                    l('SuccessfullyDeleted')
                                                );
                                                dataTable.ajax.reload();
                                            });
                                    }
                                }
                            ]
                    }
                },
                {
                    title: l('Name'),
                    data: "name"
                },
                {
                    title: l('BirthDate'),
                    data: "birthDate",
                    render: function (data) {
                        return luxon
                            .DateTime
                            .fromISO(data, {
                                locale: abp.localization.currentCulture.name
                            }).toLocaleString();
                    }
                }
            ]
        })
    );

    createModal.onResult(function () {
        dataTable.ajax.reload();
    });

    editModal.onResult(function () {
        dataTable.ajax.reload();
    });

    $('#NewAuthorButton').click(function (e) {
        e.preventDefault();
        createModal.open();
    });
});

简而言之,这个JavaScript页面;

  • 创建一个带有ActionsNameBirthDate列的数据表。
    • Actions列用于添加编辑删除操作。
    • BirthDate提供了一个render函数来使用luxon库格式化DateTime值。
  • 使用abp.ModalManager打开创建和编辑模态表单。

此代码与之前创建的“图书(Books)”页面非常相似,因此我们将不再对其进行详细说明。

本地化

该页面使用了一些我们需要声明的本地化关键字。打开Acme.BookStore.Domain.Shared项目Localization/BookStore文件夹下的en.json文件,并添加以下条目:

"Menu:Authors": "Authors",
"Authors": "Authors",
"AuthorDeletionConfirmationMessage": "Are you sure to delete the author '{0}'?",
"BirthDate": "Birth date",
"NewAuthor": "New author"

注意,我们添加了更多键。它们将在下一节中使用。

添加到主菜单

Acme.BookStore.Web项目的Menus文件夹中打开BookStoreMenuContributor.cs,然后在ConfigureMainMenuAsync方法末尾添加以下代码:

if (await context.IsGrantedAsync(BookStorePermissions.Authors.Default))
{
    bookStoreMenu.AddItem(new ApplicationMenuItem(
        "BooksStore.Authors",
        l["Menu:Authors"],
        url: "/Authors"
    ));
}

运行应用程序

运行并登录到该应用程序。您尚未获得权限,因此无法看到菜单项。转到Identity/Roles页面,单击“操作(Actions)”按钮,然后为管理员角色选择权限操作:
在这里插入图片描述

如您所见,管理员角色还没有作者管理权限。单击复选框,然后保存模态以授予必要的权限。刷新页面后,您将在主菜单中的“书店(Book Store)”下看到“作者(Authors)”菜单项:
在这里插入图片描述

除“新作者(New author)”和“操作/编辑(Actions/Edit)”外,该页面可以正常使用,因为我们尚未实现它们。

提示:如果在定义新权限后运行.DbMigrator控制台应用程序,它将自动将这些新权限授予管理员角色,并且您无需自己手动授予权限。

创建模态

Acme.BookStore.Web项目Pages/Authors文件夹下创建一个新的CreateModal.cshtml razor页面,并按如下所示更改内容。

CreateModal.cshtml

@page
@using Acme.BookStore.Localization
@using Acme.BookStore.Web.Pages.Authors
@using Microsoft.Extensions.Localization
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@model CreateModalModel
@inject IStringLocalizer<BookStoreResource> L
@{
    Layout = null;
}
<form asp-page="/Authors/CreateModal">
    <abp-modal>
        <abp-modal-header title="@L["NewAuthor"].Value"></abp-modal-header>
        <abp-modal-body>
            <abp-input asp-for="Author.Name" />
            <abp-input asp-for="Author.BirthDate" />
            <abp-input asp-for="Author.ShortBio" />
        </abp-modal-body>
        <abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)"></abp-modal-footer>
    </abp-modal>
</form>

之前,我们在书籍页面上使用了ABP框架的动态表单。我们可以在此处使用相同的方法,但我们想展示如何手动进行。实际上,不是手动操作,因为在这种情况下,我们使用了abp-input标签帮助程序来简化表单元素的创建。

您绝对可以使用标准的Bootstrap HTML结构,但这需要编写大量代码。根据数据类型abp-input自动添加验证,本地化和其他标准元素。

CreateModal.cshtml.cs

using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Acme.BookStore.Authors;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form;

namespace Acme.BookStore.Web.Pages.Authors
{
    public class CreateModalModel : BookStorePageModel
    {
        [BindProperty]
        public CreateAuthorViewModel Author { get; set; }

        private readonly IAuthorAppService _authorAppService;

        public CreateModalModel(IAuthorAppService authorAppService)
        {
            _authorAppService = authorAppService;
        }

        public void OnGet()
        {
            Author = new CreateAuthorViewModel();
        }

        public async Task<IActionResult> OnPostAsync()
        {
            var dto = ObjectMapper.Map<CreateAuthorViewModel, CreateAuthorDto>(Author);
            await _authorAppService.CreateAsync(dto);
            return NoContent();
        }

        public class CreateAuthorViewModel
        {
            [Required]
            [StringLength(AuthorConsts.MaxNameLength)]
            public string Name { get; set; }

            [Required]
            [DataType(DataType.Date)]
            public DateTime BirthDate { get; set; }

            [TextArea]
            public string ShortBio { get; set; }
        }
    }
}

此页面模型类仅注入并使用IAuthorAppService来创建新作者。书籍创建模型类之间的主要区别在于,该类为CreateAuthorViewModel视图模型声明了一个新类,而不是重用CreateAuthorDto

做出此决定的主要原因是向您展示如何在页面内使用其他模型类。但是还有另一个好处:我们为类成员添加了两个属性,它们在CreateAuthorDto中没有:

  • BirthDate添加了[DataType(DataType.Date)]属性,该属性在UI上显示了此属性的日期选择器。

  • ShortBio添加了[TextArea]属性,该属性显示多行文本区域而不是标准文本框。

这样,您可以根据UI需求专门化视图模型类,而不涉及DTO。由于此决定,我们使用ObjectMapper映射CreateAuthorViewModelCreateAuthorDto。为此,您需要向BookStoreWebAutoMapperProfile构造函数添加新的映射代码:

using Acme.BookStore.Authors; // ADDED NAMESPACE IMPORT
using Acme.BookStore.Books;
using AutoMapper;

namespace Acme.BookStore.Web
{
    public class BookStoreWebAutoMapperProfile : Profile
    {
        public BookStoreWebAutoMapperProfile()
        {
            CreateMap<BookDto, CreateUpdateBookDto>();

            // ADD a NEW MAPPING
            CreateMap<Pages.Authors.CreateModalModel.CreateAuthorViewModel,
                      CreateAuthorDto>();
        }
    }
}

当您再次运行该应用程序时,“新作者(New author)”按钮将按预期工作并打开一个新模型:
在这里插入图片描述

编辑模态

Acme.BookStore.Web项目Pages/Authors文件夹下创建一个新的EditModal.cshtml razor页面,并按如下所示更改内容。

EditModal.cshtml

@page
@using Acme.BookStore.Localization
@using Acme.BookStore.Web.Pages.Authors
@using Microsoft.Extensions.Localization
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@model EditModalModel
@inject IStringLocalizer<BookStoreResource> L
@{
    Layout = null;
}
<form asp-page="/Authors/EditModal">
    <abp-modal>
        <abp-modal-header title="@L["Update"].Value"></abp-modal-header>
        <abp-modal-body>
            <abp-input asp-for="Author.Id" />
            <abp-input asp-for="Author.Name" />
            <abp-input asp-for="Author.BirthDate" />
            <abp-input asp-for="Author.ShortBio" />
        </abp-modal-body>
        <abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)"></abp-modal-footer>
    </abp-modal>
</form>

EditModal.cshtml.cs

using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Acme.BookStore.Authors;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form;

namespace Acme.BookStore.Web.Pages.Authors
{
    public class EditModalModel : BookStorePageModel
    {
        [BindProperty]
        public EditAuthorViewModel Author { get; set; }

        private readonly IAuthorAppService _authorAppService;

        public EditModalModel(IAuthorAppService authorAppService)
        {
            _authorAppService = authorAppService;
        }

        public async Task OnGetAsync(Guid id)
        {
            var authorDto = await _authorAppService.GetAsync(id);
            Author = ObjectMapper.Map<AuthorDto, EditAuthorViewModel>(authorDto);
        }

        public async Task<IActionResult> OnPostAsync()
        {
            await _authorAppService.UpdateAsync(
                Author.Id,
                ObjectMapper.Map<EditAuthorViewModel, UpdateAuthorDto>(Author)
            );

            return NoContent();
        }

        public class EditAuthorViewModel
        {
            [HiddenInput]
            public Guid Id { get; set; }

            [Required]
            [StringLength(AuthorConsts.MaxNameLength)]
            public string Name { get; set; }

            [Required]
            [DataType(DataType.Date)]
            public DateTime BirthDate { get; set; }

            [TextArea]
            public string ShortBio { get; set; }
        }
    }
}

此类类似于CreateModal.cshtml.cs,但有一些主要区别。

  • 使用IAuthorAppService.GetAsync(...)方法从应用程序层获取编辑作者。

  • EditAuthorViewModel有一个附加Id属性,它被标记为[HiddenInput]属性,该属性为该属性创建一个隐藏的输入。

该类需要向该类添加两个对象映射声明BookStoreWebAutoMapperProfile:

using Acme.BookStore.Authors;
using Acme.BookStore.Books;
using AutoMapper;

namespace Acme.BookStore.Web
{
    public class BookStoreWebAutoMapperProfile : Profile
    {
        public BookStoreWebAutoMapperProfile()
        {
            CreateMap<BookDto, CreateUpdateBookDto>();

            CreateMap<Pages.Authors.CreateModalModel.CreateAuthorViewModel,
                      CreateAuthorDto>();

            // ADD THESE NEW MAPPINGS
            CreateMap<AuthorDto, Pages.Authors.EditModalModel.EditAuthorViewModel>();
            CreateMap<Pages.Authors.EditModalModel.EditAuthorViewModel,
                      UpdateAuthorDto>();
        }
    }
}

就这样!您可以运行该应用程序并尝试编辑作者。

下一部分

请参阅本教程的下一部分

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值