Convert Create/Edit Modals to Page
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
toBooks.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 addGoToEditPage
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
Comments
Alper Ebiçoğlu 85 weeks ago
thanks for sharing how to convert the modal content to plain razor pages.
Onur Pıçakcı 85 weeks ago
Great article!
Berkan Şaşmaz 85 weeks ago
Great article!
Enis Necipoğlu 84 weeks ago
Looking for MVC? Here mvc version of this arcitle: https://community.abp.io/posts/converting-createedit-modal-to-page-4ps5v60m
Val Kelmendi 84 weeks ago
looking for angular
Masum ULU 82 weeks ago
Hi Val, Here's Angular version https://community.abp.io/posts/converting-createedit-modal-to-page-angularui-doadhgil
Moriancumer 66 weeks ago
I don't know if this message comes here, but I am learning to use ABP framework trying to reproduce the BookStore Application using Blazor and Entity Framework method but when I add this line
@code { public Books() // Constructor { CreatePolicyName = BookStorePermissions.Books.Create; UpdatePolicyName = BookStorePermissions.Books.Edit; DeletePolicyName = BookStorePermissions.Books.Delete; } }
I get this compilation error "Method must have a return type" I can't go on because of this error, any tips or someone that has faced a similar problem?
Thanks in advance
Enis Necipoğlu 66 weeks ago
If it's a constructor, then its name must be the same with class name. If it's a razor page, it's better to use
override OnInitializedAsync()
method instead constructors