Web Application Development Tutorial - Part 3: Creating, Updating and Deleting Books
About This Tutorial
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:
- Entity Framework Core as the database provider.
- Blazor as the UI Framework.
This tutorial is organized as the following parts;
- Part 1: Creating the project and book list page
- Part 2: The book list page
- Part 3: Creating, updating and deleting books (this part)
- Part 4: Integration tests
- Part 5: Authorization
- Part 6: Authors: Domain layer
- Part 7: Authors: Database Integration
- Part 8: Authors: Application Layer
- Part 9: Authors: User Interface
- Part 10: Book to Author Relation
Download the Source Code
This tutorial has multiple versions based on your UI and Database preferences. We've prepared a few combinations of the source code to be downloaded:
Creating a New Book
In this section, you will learn how to create a new modal dialog form to create a new book. Since we've inherited from the AbpCrudPageBase, we only need to develop the view part.
Add "New Button" Button
Open the Books.razor and replace the <CardHeader> section with the following code:
<CardHeader>
    <Row>
        <Column ColumnSize="ColumnSize.Is6">
            <h2>@L["Books"]</h2>
        </Column>
        <Column ColumnSize="ColumnSize.Is6">
            <Paragraph Alignment="TextAlignment.Right">
                <Button Color="Color.Primary"
                        Clicked="OpenCreateModalAsync">@L["NewBook"]</Button>
            </Paragraph>
        </Column>
    </Row>
</CardHeader>
This will change the card header by adding a "New book" button to the right side:

Now, we can add a modal that will be opened when we click to the button.
Book Creation Modal
Open the Books.razor and add the following code to the end of the page:
<Modal @ref="CreateModal">
    <ModalBackdrop />
    <ModalContent IsCentered="true">
        <ModalHeader>
            <ModalTitle>@L["NewBook"]</ModalTitle>
            <CloseButton Clicked="CloseCreateModalAsync" />
        </ModalHeader>
        <ModalBody>
            <Field>
                <FieldLabel>@L["Name"]</FieldLabel>
                <TextEdit @bind-text="NewEntity.Name" />
            </Field>
            <Field>
                <FieldLabel>@L["Type"]</FieldLabel>
                <Select TValue="BookType" @bind-SelectedValue="@NewEntity.Type">
                    @foreach (int bookTypeValue in Enum.GetValues(typeof(BookType)))
                    {
                        <SelectItem TValue="BookType" Value="@((BookType)bookTypeValue)">
                            @L[$"Enum:BookType:{bookTypeValue}"]
                        </SelectItem>
                    }
                </Select>
            </Field>
            <Field>
                <FieldLabel>@L["PublishDate"]</FieldLabel>
                <DateEdit TValue="DateTime" @bind-Date="NewEntity.PublishDate" />
            </Field>
            <Field>
                <FieldLabel>@L["Price"]</FieldLabel>
                <NumericEdit TValue="float" @bind-Value="NewEntity.Price" />
            </Field>
        </ModalBody>
        <ModalFooter>
            <Button Color="Color.Secondary" 
                    Clicked="CloseCreateModalAsync">@L["Cancel"]</Button>
            <Button Color="Color.Primary" 
                    Clicked="CreateEntityAsync">@L["Save"]</Button>
        </ModalFooter>
    </ModalContent>
</Modal>
- CreateModalobject,- CloseCreateModalAsyncand- CreateEntityAsyncmethod are defined by the base class. See the Blazorise documentation if you want to understand the- Modaland the other components.
That's all. Run the application and try to add a new book:

Updating a Book
Editing a books is similar to the creating a new book.
Actions Dropdown
Open the Books.razor and add the following DataGridColumn section inside the DataGridColumns as the first item:
<DataGridColumn Width="150px" 
                TItem="BookDto"
                Field="@nameof(BookDto.Id)" 
                Sortable="false" 
                Caption="@L["Actions"]">
    <DisplayTemplate>
        <Dropdown>
            <DropdownToggle Color="Color.Primary">
                @L["Actions"]
            </DropdownToggle>
            <DropdownMenu>
                <DropdownItem Clicked="() => OpenEditModalAsync(context.Id)">
                    @L["Edit"]
                </DropdownItem>
            </DropdownMenu>
        </Dropdown>
    </DisplayTemplate>
</DataGridColumn>
- OpenEditModalAsyncis defined in the base class which takes the- Idof the entity (book) to edit.
This adds an "Actions" dropdown to all the books inside the DataGrid with an Edit action:

Edit Modal
We can now define a modal to edit the book. Add the following code to the end of the Books.razor page:
<Modal @ref="EditModal">
    <ModalBackdrop />
    <ModalContent IsCentered="true">
        <ModalHeader>
            <ModalTitle>@EditingEntity.Name</ModalTitle>
            <CloseButton Clicked="CloseEditModalAsync" />
        </ModalHeader>
        <ModalBody>
            <Field>
                <FieldLabel>@L["Name"]</FieldLabel>
                <TextEdit @bind-text="EditingEntity.Name" />
            </Field>
            <Field>
                <FieldLabel>@L["Type"]</FieldLabel>
                <Select TValue="BookType" @bind-SelectedValue="@EditingEntity.Type">
                    @foreach (int bookTypeValue in Enum.GetValues(typeof(BookType)))
                    {
                        <SelectItem TValue="BookType" Value="@((BookType)bookTypeValue)">
                            @L[$"Enum:BookType:{bookTypeValue}"]
                        </SelectItem>
                    }
                </Select>
            </Field>
            <Field>
                <FieldLabel>@L["PublishDate"]</FieldLabel>
                <DateEdit TValue="DateTime" @bind-Date="EditingEntity.PublishDate" />
            </Field>
            <Field>
                <FieldLabel>@L["Price"]</FieldLabel>
                <NumericEdit TValue="float" @bind-Value="EditingEntity.Price" />
            </Field>
        </ModalBody>
        <ModalFooter>
            <Button Color="Color.Secondary"
                    Clicked="CloseEditModalAsync">@L["Cancel"]</Button>
            <Button Color="Color.Primary"
                    Clicked="UpdateEntityAsync">@L["Save"]</Button>
        </ModalFooter>
    </ModalContent>
</Modal>
AutoMapper Configuration
The base AbpCrudPageBase uses the object to object mapping system to convert an incoming BookDto object to a CreateUpdateBookDto object. So, we need to define the mapping.
Open the BookStoreBlazorAutoMapperProfile inside the Acme.BookStore.Blazor project and change the content as the following:
using Acme.BookStore.Books;
using AutoMapper;
namespace Acme.BookStore.Blazor
{
    public class BookStoreBlazorAutoMapperProfile : Profile
    {
        public BookStoreBlazorAutoMapperProfile()
        {
            CreateMap<BookDto, CreateUpdateBookDto>();
        }
    }
}
- We've just added the CreateMap<BookDto, CreateUpdateBookDto>();line to define the mapping.
Test the Editing Modal
You can now run the application and try to edit a book.

Deleting a Book
Open the Books.razor page and add the following DropdownItem under the "Edit" action inside the "Actions" DropdownMenu:
<DropdownItem Clicked="() => DeleteEntityAsync(context)">
    @L["Delete"]
</DropdownItem>
- DeleteEntityAsyncis defined in the base class.
Run the application and try to delete a book.
Full CRUD UI Code
Here the complete code to create the book management CRUD page, that has been developed in the last two parts:
@page "/books"
@using Volo.Abp.Application.Dtos
@using Volo.Abp.BlazoriseUI
@using Acme.BookStore.Books
@using Acme.BookStore.Localization
@using Microsoft.Extensions.Localization
@inject IStringLocalizer<BookStoreResource> L
@inherits AbpCrudPageBase<IBookAppService, BookDto, Guid, PagedAndSortedResultRequestDto, CreateUpdateBookDto>
<Card>
    <CardHeader>
        <Row>
            <Column ColumnSize="ColumnSize.Is6">
                <h2>@L["Books"]</h2>
            </Column>
            <Column ColumnSize="ColumnSize.Is6">
                <Paragraph Alignment="TextAlignment.Right">
                    <Button Color="Color.Primary"
                            Clicked="OpenCreateModalAsync">
                        @L["NewBook"]
                    </Button>
                </Paragraph>
            </Column>
        </Row>
    </CardHeader>
    <CardBody>
        <DataGrid TItem="BookDto"
                  Data="Entities"
                  ReadData="OnDataGridReadAsync"
                  TotalItems="TotalCount"
                  ShowPager="true"
                  PageSize="PageSize">
            <DataGridColumns>
                <DataGridColumn Width="150px" 
                                TItem="BookDto"
                                Field="@nameof(BookDto.Id)" 
                                Sortable="false" 
                                Caption="@L["Actions"]">
                    <DisplayTemplate>
                        <Dropdown>
                            <DropdownToggle Color="Color.Primary">
                                @L["Actions"]
                            </DropdownToggle>
                            <DropdownMenu>
                                <DropdownItem Clicked="() => OpenEditModalAsync(context.Id)">
                                    @L["Edit"]
                                </DropdownItem>
                                <DropdownItem Clicked="() => DeleteEntityAsync(context)">
                                    @L["Delete"]
                                </DropdownItem>
                            </DropdownMenu>
                        </Dropdown>
                    </DisplayTemplate>
                </DataGridColumn>
                <DataGridColumn TItem="BookDto"
                                Field="@nameof(BookDto.Name)"
                                Caption="@L["Name"]"></DataGridColumn>
                <DataGridColumn TItem="BookDto"
                                Field="@nameof(BookDto.Type)"
                                Caption="@L["Type"]">
                    <DisplayTemplate>
                        @L[$"Enum:BookType:{(int) context.Type}"]
                    </DisplayTemplate>
                </DataGridColumn>
                <DataGridColumn TItem="BookDto"
                                Field="@nameof(BookDto.PublishDate)"
                                Caption="@L["PublishDate"]">
                    <DisplayTemplate>
                        @context.PublishDate.ToShortDateString()
                    </DisplayTemplate>
                </DataGridColumn>
                <DataGridColumn TItem="BookDto"
                                Field="@nameof(BookDto.Price)"
                                Caption="@L["Price"]">
                </DataGridColumn>
                <DataGridColumn TItem="BookDto"
                                Field="@nameof(BookDto.CreationTime)"
                                Caption="@L["CreationTime"]">
                    <DisplayTemplate>
                        @context.CreationTime.ToLongDateString()
                    </DisplayTemplate>
                </DataGridColumn>
            </DataGridColumns>
        </DataGrid>
    </CardBody>
</Card>
<Modal @ref="CreateModal">
    <ModalBackdrop />
    <ModalContent IsCentered="true">
        <ModalHeader>
            <ModalTitle>@L["NewBook"]</ModalTitle>
            <CloseButton Clicked="CloseCreateModalAsync" />
        </ModalHeader>
        <ModalBody>
            <Field>
                <FieldLabel>@L["Name"]</FieldLabel>
                <TextEdit @bind-text="NewEntity.Name" />
            </Field>
            <Field>
                <FieldLabel>@L["Type"]</FieldLabel>
                <Select TValue="BookType" @bind-SelectedValue="@NewEntity.Type">
                    @foreach (int bookTypeValue in Enum.GetValues(typeof(BookType)))
                    {
                        <SelectItem TValue="BookType" Value="@((BookType)bookTypeValue)">
                            @L[$"Enum:BookType:{bookTypeValue}"]
                        </SelectItem>
                    }
                </Select>
            </Field>
            <Field>
                <FieldLabel>@L["PublishDate"]</FieldLabel>
                <DateEdit TValue="DateTime" @bind-Date="NewEntity.PublishDate" />
            </Field>
            <Field>
                <FieldLabel>@L["Price"]</FieldLabel>
                <NumericEdit TValue="float" @bind-Value="NewEntity.Price" />
            </Field>
        </ModalBody>
        <ModalFooter>
            <Button Color="Color.Secondary" 
                    Clicked="CloseCreateModalAsync">@L["Cancel"]</Button>
            <Button Color="Color.Primary" 
                    Clicked="CreateEntityAsync">@L["Save"]</Button>
        </ModalFooter>
    </ModalContent>
</Modal>
<Modal @ref="EditModal">
    <ModalBackdrop />
    <ModalContent IsCentered="true">
        <ModalHeader>
            <ModalTitle>@EditingEntity.Name</ModalTitle>
            <CloseButton Clicked="CloseEditModalAsync" />
        </ModalHeader>
        <ModalBody>
            <Field>
                <FieldLabel>@L["Name"]</FieldLabel>
                <TextEdit @bind-text="EditingEntity.Name" />
            </Field>
            <Field>
                <FieldLabel>@L["Type"]</FieldLabel>
                <Select TValue="BookType" @bind-SelectedValue="@EditingEntity.Type">
                    @foreach (int bookTypeValue in Enum.GetValues(typeof(BookType)))
                    {
                        <SelectItem TValue="BookType" Value="@((BookType)bookTypeValue)">
                            @L[$"Enum:BookType:{bookTypeValue}"]
                        </SelectItem>
                    }
                </Select>
            </Field>
            <Field>
                <FieldLabel>@L["PublishDate"]</FieldLabel>
                <DateEdit TValue="DateTime" @bind-Date="EditingEntity.PublishDate" />
            </Field>
            <Field>
                <FieldLabel>@L["Price"]</FieldLabel>
                <NumericEdit TValue="float" @bind-Value="EditingEntity.Price" />
            </Field>
        </ModalBody>
        <ModalFooter>
            <Button Color="Color.Secondary"
                    Clicked="CloseEditModalAsync">@L["Cancel"]</Button>
            <Button Color="Color.Primary"
                    Clicked="UpdateEntityAsync">@L["Save"]</Button>
        </ModalFooter>
    </ModalContent>
</Modal>
The Next Part
See the next part of this tutorial.
 
                                             
                                    