Converting Create/Edit Modal to Page — Blazor

ABP.IO
abp-community
Published in
4 min readApr 13, 2023

In this document we will explain how to convert BookStore’s Books create & edit modals to regular blazor pages.

Before

After

Books.razor Page

Books.razor page is the main page of the books management. Create & Update operations are done in this page. So we’ll remove create & update operations from this page and move a separate blazor component for each operation. Each component will be a page.

  • Remove both Create & Update modals.
  • Replace NewBook button with a link to CreateBook page.
<Button Color="Color.Primary" Type="ButtonType.Link" To="books/new">
@L["NewBook"]
</Button>
  • Inject NavigationManager to Books.razor page.
@inject NavigationManager NavigationManager
  • Replace Edit button with a link to UpdateBook page.
<Button Color="Color.Primary" Type="ButtonType.Link" OnClick="() => NavigateToEdit(book.Id)">
@L["Edit"]
</Button>
private void NavigateToEdit(Guid id)
{
NavigationManager.NavigateTo($"books/{id}/edit");
}
  • Remove all methods in the Books.razor page except constructor. And add GoToEditPage as below:
protected void GoToEditPage(BookDto book)
{
NavigationManager.NavigateTo($"books/{book.Id}");
}
  • Change Edit button to a link in the table.
<EntityAction TItem="BookDto"
Text="@L["Edit"]"
Visible=HasUpdatePermission
Clicked="() => GoToEditPage(context)" />

CreateBooks Page

Create new CreateBook.razor and CreateBook.razor.cs files in your project.

  • CreateBook.razor
@page "/books/new"
@attribute [Authorize(BookStorePermissions.Books.Create)]
@inherits BookStoreComponentBase

@using Acme.BookStore.Books;
@using Acme.BookStore.Localization;
@using Acme.BookStore.Permissions;
@using Microsoft.Extensions.Localization;
@using Volo.Abp.AspNetCore.Components.Web;

@inject IStringLocalizer<BookStoreResource> L
@inject AbpBlazorMessageLocalizerHelper<BookStoreResource> LH
@inject IBookAppService AppService
@inject NavigationManager NavigationManager

<Card>
<CardHeader>
<HeadContent>
<ModalTitle>@L["NewBook"]</ModalTitle>
</HeadContent>
</CardHeader>
<CardBody>
<Validations @ref="@CreateValidationsRef" Model="@NewEntity" ValidateOnLoad="false">
<Validation MessageLocalizer="@LH.Localize">
<Field>
<FieldLabel>@L["Author"]</FieldLabel>
<Select TValue="Guid" @bind-SelectedValue="@NewEntity.AuthorId">
@foreach (var author in authorList)
{
<SelectItem TValue="Guid" Value="@author.Id">
@author.Name
</SelectItem>
}
</Select>
</Field>
<Field>
<FieldLabel>@L["Name"]</FieldLabel>
<TextEdit @bind-Text="@NewEntity.Name">
<Feedback>
<ValidationError />
</Feedback>
</TextEdit>
</Field>
</Validation>
<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>
</Validations>
</CardBody>
<CardFooter>
<Button Color="Color.Secondary" Type="ButtonType.Link" To="books">
@L["Cancel"]
</Button>
<Button Color="Color.Primary"
Type="@ButtonType.Submit"
PreventDefaultOnSubmit="true"
Clicked="CreateEntityAsync">
@L["Save"]
</Button>
</CardFooter>
</Card>
  • CreateBook.razor.cs
using Acme.BookStore.Books;
using Blazorise;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp;

namespace Acme.BookStore.Blazor.Pages;

public partial class CreateBook
{
protected Validations CreateValidationsRef;
protected CreateUpdateBookDto NewEntity = new();
IReadOnlyList<AuthorLookupDto> authorList = Array.Empty<AuthorLookupDto>();

protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
authorList = (await AppService.GetAuthorLookupAsync()).Items;

if (!authorList.Any())
{
throw new UserFriendlyException(message: L["AnAuthorIsRequiredForCreatingBook"]);
}

NewEntity.AuthorId = authorList.First().Id;

if (CreateValidationsRef != null)
{
await CreateValidationsRef.ClearAll();
}
}

protected virtual async Task CreateEntityAsync()
{
try
{
var validate = true;
if (CreateValidationsRef != null)
{
validate = await CreateValidationsRef.ValidateAll();
}
if (validate)
{
await AppService.CreateAsync(NewEntity);
NavigationManager.NavigateTo("books");
}
}
catch (Exception ex)
{
await HandleErrorAsync(ex);
}
}
}

EditBooks Page

Create new EditBook.razor and EditBook.razor.cs files in your project.

  • EditBook.razor
@page "/books/{Id}"
@attribute [Authorize(BookStorePermissions.Books.Edit)]
@inherits BookStoreComponentBase
@using Acme.BookStore.Books;
@using Acme.BookStore.Localization;
@using Acme.BookStore.Permissions;
@using Microsoft.Extensions.Localization;
@using Volo.Abp.AspNetCore.Components.Web;

@inject IStringLocalizer<BookStoreResource> L
@inject AbpBlazorMessageLocalizerHelper<BookStoreResource> LH
@inject IBookAppService AppService
@inject NavigationManager NavigationManager

<Card>
<CardHeader>
<HeadContent>
<ModalTitle>@EditingEntity.Name</ModalTitle>
</HeadContent>
</CardHeader>
<CardBody>
<Validations @ref="@EditValidationsRef" Model="@EditingEntity" ValidateOnLoad="false">
<Validation MessageLocalizer="@LH.Localize">
<Field>
<FieldLabel>@L["Author"]</FieldLabel>
<Select TValue="Guid" @bind-SelectedValue="@EditingEntity.AuthorId">
@foreach (var author in authorList)
{
<SelectItem TValue="Guid" Value="@author.Id">
@author.Name
</SelectItem>
}
</Select>
</Field>
<Field>
<FieldLabel>@L["Name"]</FieldLabel>
<TextEdit @bind-Text="@EditingEntity.Name">
<Feedback>
<ValidationError />
</Feedback>
</TextEdit>
</Field>
</Validation>
<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>
</Validations>
</CardBody>
<CardFooter>
<Button Color="Color.Secondary" Type="ButtonType.Link" To="books">
@L["Cancel"]
</Button>
<Button Color="Color.Primary"
Type="@ButtonType.Submit"
PreventDefaultOnSubmit="true"
Clicked="UpdateEntityAsync">
@L["Save"]
</Button>
</CardFooter>
</Card>
  • EditBook.razor.cs
using Acme.BookStore.Books;
using Blazorise;
using Microsoft.AspNetCore.Components;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp;

namespace Acme.BookStore.Blazor.Pages;

public partial class EditBook
{
protected CreateUpdateBookDto EditingEntity = new();
protected Validations EditValidationsRef;
IReadOnlyList<AuthorLookupDto> authorList = Array.Empty<AuthorLookupDto>();

[Parameter]
public string Id { get; set; }

public Guid EditingEntityId { get; set; }

protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();

// Blazor can't parse Guid as route constraint currently.
// See https://github.com/dotnet/aspnetcore/issues/19008
EditingEntityId = Guid.Parse(Id);

authorList = (await AppService.GetAuthorLookupAsync()).Items;

if (!authorList.Any())
{
throw new UserFriendlyException(message: L["AnAuthorIsRequiredForCreatingBook"]);
}

var entityDto = await AppService.GetAsync(EditingEntityId);

EditingEntity = ObjectMapper.Map<BookDto,CreateUpdateBookDto>(entityDto);

if (EditValidationsRef != null)
{
await EditValidationsRef.ClearAll();
}
}

protected virtual async Task UpdateEntityAsync()
{
try
{
var validate = true;
if (EditValidationsRef != null)
{
validate = await EditValidationsRef.ValidateAll();
}
if (validate)
{
await AppService.UpdateAsync(EditingEntityId, EditingEntity);

NavigationManager.NavigateTo("books");
}
}
catch (Exception ex)
{
await HandleErrorAsync(ex);
}
}
}

You can check the following commit for details: https://github.com/abpframework/abp-samples/commit/aae61ad6d66ebf6191dd4dcfb4e23d30bd680a4e

--

--

ABP.IO
abp-community

Open Source ABP Framework is a complete infrastructure to create modern web applications by following the best practices & conventions of software development.