我是靠谱客的博主 勤恳大白,最近开发中收集的这篇文章主要介绍在Blazor中构建数据库应用程序——第5部分——查看组件——UI中的CRUD列表操作介绍储存库和数据库列表功能基本表单总结,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

目录

介绍

储存库和数据库

列表功能

基本表单

分页

初始表单加载

表单事件

页面控件

WeatherForecastListForm

WeatherForcastListModalView

总结


介绍

这是该系列文章的第五篇,探讨了如何在Blazor中构建和构建真正的数据库应用程序。

  1. 项目结构与框架
  2. 服务——构建CRUD数据层
  3. View组件——UI中的CRUD编辑和查看操作
  4. UI组件——构建HTML / CSS控件
  5. View组件-UI中的CRUD列表操作
  6. 逐步详细介绍如何向应用程序添加气象站和气象站数据

本文详细介绍了构建可重用的列表表示层组件并将其部署在ServerWASM项目中的情况。

储存库和数据库

CEC.Blazor GitHub存储库

存储库中有一个SQL脚本在/SQL中,用于构建数据库。

您可以在此处查看运行的项目的服务器版本。

你可以看到该项目的WASM版本运行在这里

列表功能

列表组件比其他CRUD组件面临的挑战要多得多。专业级别列表控件中预期的功能包括:

  • 分页以处理大型数据集
  • 列格式化以控制列宽和数据溢出
  • 在各个列上排序
  • 筛选

基本表单

ListFormBase是所有列表的基本表单。它继承自ControllerServiceFormBase

文章中并没有显示所有代码——有些类太大了,我只显示最相关的部分。可以在Github站点上查看所有源文件,并且在本文的适当位置提供了对特定代码文件的引用或链接。代码注释中有关于代码段的详细信息。

分页

分页是通过IControllerPagingService接口实现的。BaseControllerService实现此接口。由于太大,此处未详细显示。许多功能非常明显——可以跟踪您所在页面的属性,具有多少页面和块、页面大小等的属性——因此我们将跳到更有趣的部分。

初始表单加载

让我们从加载列表表单开始,然后看一下OnRenderAsync

// CEC.Weather/Components/Forms/WeatherForecastListForm.razor.cs
protected override Task OnRenderAsync(bool firstRender)
{
if (firstRender)
{
// Sets the specific service
this.Service = this.ControllerService;
// Sets the max column
this.UIOptions.MaxColumn = 3;
}
return base.OnRenderAsync(firstRender);
}
// CEC.Blazor/Components/BaseForms/ListFormBase.cs
protected async override Task OnRenderAsync(bool firstRender)
{
// set the page to 1
var page = 1;
if (this.IsService)
{
if (firstRender)
{
// Reset the Service if this is the first load
await this.Service.Reset();
this.ListTitle = string.IsNullOrEmpty(this.ListTitle) ?
$"List of {this.Service.RecordConfiguration.RecordListDecription}" :
this.ListTitle;
}
// Load the filters for the recordset
this.LoadFilter();
// Check if we have a saved Page No in the ViewData
if (this.IsViewManager &&
!this.ViewManager.ViewData.GetFieldAsInt("Page", out page)) page = 1;
// Load the paged recordset
await this.Service.LoadPagingAsync();
// go to the correct page
await this.Paging.GoToPageAsync(page);
this.Loading = false;
}
await base.OnRenderAsync(firstRender);
}
// CEC.Blazor/Components/BaseForms/FormBase.cs
protected async override Task OnRenderAsync(bool firstRender)
{
if (firstRender) {
await GetUserAsync();
}
await base.OnRenderAsync(firstRender);
}
// base LoadFilter
protected virtual void LoadFilter()
{
// Set OnlyLoadIfFilter if the Parameter value is set
if (IsService) this.Service.FilterList.OnlyLoadIfFilters = this.OnlyLoadIfFilter;
}

我们有兴趣ListFormBase调用LoadPagingAsync()

// CEC.Blazor/Components/BaseForms/ListComponentBase.cs
/// Method to load up the Paged Data to display
/// loads the delegate with the default service GetDataPage method and loads the first page
/// Can be overridden for more complex situations
public async virtual Task LoadPagingAsync()
{
// set the record to null to force a reload of the records
this.Records = null;
// if requested adds a default service function to the delegate
this.PageLoaderAsync = new IControllerPagingService<TRecord>.PageLoaderDelegateAsync
(this.GetDataPageWithSortingAsync);
// loads the paging object
await this.LoadAsync();
// Trigger event so any listeners get notified
this.TriggerListChangedEvent(this);
}

IControllerPagingService定义一个返回TRecordsListPageLoaderDelegateAsync委托和一个委托属性PageLoaderAsync

// CEC.Blazor/Services/Interfaces/IControllerPagingService.cs
// Delegate that returns a generic Record List
public delegate Task<List<TRecord>> PageLoaderDelegateAsync();
// Holds the reference to the PageLoader Method to use
public PageLoaderDelegateAsync PageLoaderAsync { get; set; }

默认情况下,LoadPagingAsync将方法GetDataPageWithSortingAsync加载为PageLoaderAsync,然后调用LoadAsync

LoadAsync重置各种属性(我们在OnRenderAsync中通过重置服务来完成此操作,但是LoadAsync在需要重置记录列表的其他地方调用了它-而不是整个服务),然后调用了PageLoaderAsync委托。

// CEC.Blazor/Services/BaseControllerService.cs
public async Task<bool> LoadAsync()
{
// Reset the page to 1
this.CurrentPage = 1;
// Check if we have a sort column, if not set to the default column
if (!string.IsNullOrEmpty(this.DefaultSortColumn))
this.SortColumn = this.DefaultSortColumn;
// Set the sort direction to the default
this.SortingDirection = DefaultSortingDirection;
// Check if we have a method loaded in the PageLoaderAsync delegate and if so run it
if (this.PageLoaderAsync != null) this.PagedRecords = await this.PageLoaderAsync();
// Set the block back to the start
this.ChangeBlock(0);
//
Force a UI update as everything has changed
this.PageHasChanged?.Invoke(this, this.CurrentPage);
return true;
}

GetDataPageWithSortingAsync如下所示。有关详细信息,请参见注释。如有必要,GetFilteredListAsync总是调用来刷新列表recordset

// CEC.Blazor/Services/BaseControllerService.cs
public async virtual Task<List<TRecord>> GetDataPageWithSortingAsync()
{
// Get the filtered list - will only get a new list if the Records property
// has been set to null elsewhere
await this.GetFilteredListAsync();
// Reset the start record if we are outside
// the range of the record set - a belt and braces check as this shouldn't happen!
if (this.PageStartRecord > this.Records.Count) this.CurrentPage = 1;
// Check if we have to apply sorting, in not get the page we want
if (string.IsNullOrEmpty(this.SortColumn))
return this.Records.Skip(this.PageStartRecord).Take(this._PageSize).ToList();
else
{
//
If we do order the record set and then get the page we want
if (this.SortingDirection == SortDirection.Ascending)
{
return this.Records.OrderBy(x => x.GetType().GetProperty(this.SortColumn).
GetValue(x, null)).Skip(this.PageStartRecord).Take(this._PageSize).ToList();
}
else
{
return this.Records.OrderByDescending
(x => x.GetType().GetProperty(this.SortColumn).
GetValue(x, null)).Skip(this.PageStartRecord).Take(this._PageSize).ToList();
}
}
}

GetFilteredListAsync得到一个过滤列表recordset。在应用过滤的表单组件中(例如从过滤器控件中)覆盖了它。默认实现获取整个recordset。它使用IsRecords来检查它是否需要重新加载recordset。如果Recordsnull,则仅重新加载。

// CEC.Blazor/Services/BaseControllerService.cs
public async virtual Task<bool> GetFilteredListAsync()
{
// Check if the record set is null. and only refresh the record set if it's null
if (!this.IsRecords)
{
//gets the filtered record list
this.Records = await this.Service.GetFilteredRecordListAsync(FilterList);
return true;
}
return false;
}

总结一下:

  1. 在窗体加载Service时(记录类型的特定数据服务)将重置。
  2. Service上的GetDataPageWithSortingAsync()被加载到Service委托。
  3. Service中的Records设置为null时调用Delegate
  4. GetFilteredListAsync()加载Records
  5. GetDataPageWithSortingAsync() 加载第一页。
  6. IsLoading设置为false,以便UIErrorHandler UI控件显示页面。
  7. Form后刷新OnParametersSetAsync自动所以没有手动调用StateHasChanged是必需的。OnInitializedAsync是的一部分,OnParametersSetAsync只有完成OnInitializedAsync后才能完成。

表单事件

PagingControl通过IPagingControlService接口直接与表单Service进行交互,并将单击按钮链接到IPagingControlService方法:

  1. ChangeBlockAsync(int direction,bool supresspageupdate)
  2. MoveOnePageAsync(int direction)
  3. GoToPageAsync(int pageno)
// CEC.Blazor/Services/BaseControllerService.cs
/// Moves forward or backwards one block
/// direction 1 for forwards
/// direction -1 for backwards
/// suppresspageupdate
///
- set to true (default) when user changes page and the block changes with the page
///
- set to false when user changes block rather than changing page
/// and the page needs to be updated to the first page of the block
public async Task ChangeBlockAsync(int direction, bool suppresspageupdate = true)
{
if (direction == 1 && this.EndPage < this.TotalPages)
{
this.StartPage = this.EndPage + 1;
if (this.EndPage + this.PagingBlockSize < this.TotalPages)
this.EndPage = this.StartPage + this.PagingBlockSize - 1;
else this.EndPage = this.TotalPages;
if (!suppresspageupdate) this.CurrentPage = this.StartPage;
}
else if (direction == -1 && this.StartPage > 1)
{
this.EndPage = this.StartPage - 1;
this.StartPage = this.StartPage - this.PagingBlockSize;
if (!suppresspageupdate) this.CurrentPage = this.StartPage;
}
else if (direction == 0 && this.CurrentPage == 1)
{
this.StartPage = 1;
if (this.EndPage + this.PagingBlockSize < this.TotalPages)
this.EndPage = this.StartPage + this.PagingBlockSize - 1;
else this.EndPage = this.TotalPages;
}
if (!suppresspageupdate) await this.PaginateAsync();
}
/// Moves forward or backwards one page
/// direction 1 for forwards
/// direction -1 for backwards
public async Task MoveOnePageAsync(int direction)
{
if (direction == 1)
{
if (this.CurrentPage < this.TotalPages)
{
if (this.CurrentPage == this.EndPage) await ChangeBlockAsync(1);
this.CurrentPage += 1;
}
}
else if (direction == -1)
{
if (this.CurrentPage > 1)
{
if (this.CurrentPage == this.StartPage) await ChangeBlockAsync(-1);
this.CurrentPage -= 1;
}
}
await this.PaginateAsync();
}
/// Moves to the specified page
public Async Task GoToPageAsync(int pageno)
{
this.CurrentPage = pageno;
await this.PaginateAsync();
}

上面所有方法都设置了IPagingControlService属性,然后调用PaginateAsync(),该调用将调用PageLoaderAsync委托并强制UI更新。

// CEC.Blazor/Services/BaseControllerService.cs
/// Method to trigger the page Changed Event
public async Task PaginateAsync()
{
// Check if we have a method loaded in the PageLoaderAsync delegate and if so run it
if (this.PageLoaderAsync != null) this.PagedRecords = await this.PageLoaderAsync();
//
Force a UI update as something has changed
this.PageHasChanged?.Invoke(this, this.CurrentPage);
}

页面控件

PageControl代码如下所示,并带有注释。

// CEC.Blazor/Components/FormControls/PagingControl.razor
@if (this.IsPagination)
{
<div class="pagination ml-2 flex-nowrap">
<nav aria-label="Page navigation">
<ul class="pagination mb-1">
@if (this.DisplayType != PagingDisplayType.Narrow)
{
@if (this.DisplayType == PagingDisplayType.FullwithoutPageSize)
{
<li class="page-item"><button class="page-link"
@onclick="(e => this.Paging.ChangeBlockAsync
(-1, false))">1«</button></li>
}
else
{
<li class="page-item"><button class="page-link"
@onclick="(e => this.Paging.ChangeBlockAsync
(-1, false))">«</button></li>
}
<li class="page-item"><button class="page-link"
@onclick="(e => this.Paging.MoveOnePageAsync(-1))">Previous
</button></li>
@for (int i = this.Paging.StartPage; i <= this.Paging.EndPage; i++)
{
var currentpage = i;
<li class="page-item @(currentpage == this.Paging.CurrentPage ?
"active" : "")"><button class="page-link"
@onclick="(e => this.Paging.GoToPageAsync(currentpage))">@currentpage
</button></li>
}
<li class="page-item"><button class="page-link"
@onclick="(e => this.Paging.MoveOnePageAsync(1))">Next
</button></li>
@if (this.DisplayType == PagingDisplayType.FullwithoutPageSize)
{
<li class="page-item"><button class="page-link"
@onclick="(e => this.Paging.ChangeBlockAsync(1, false))">»
@this.Paging.TotalPages
</button></li>
}
else
{
<li class="page-item"><button class="page-link"
@onclick="(e => this.Paging.ChangeBlockAsync(1, false))">»
</button></li>
}
}
else
{
<li class="page-item"><button class="page-link"
@onclick="(e => this.Paging.MoveOnePageAsync(-1))">1«
</button></li>
@for (int i = this.Paging.StartPage; i <= this.Paging.EndPage; i++)
{
var currentpage = i;
<li class="page-item @(currentpage == this.Paging.CurrentPage ?
"active" : "")"><button class="page-link"
@onclick="(e =>
this.Paging.GoToPageAsync(currentpage))">@currentpage
</button></li>
}
<li class="page-item"><button class="page-link"
@onclick="(e => this.Paging.MoveOnePageAsync(1))">»
@this.Paging.TotalPages
</button></li>
}
</ul>
</nav>
@if (this.DisplayType == PagingDisplayType.Full)
{
<span class="pagebutton btn btn-link btn-sm disabled mr-1">Page
@this.Paging.CurrentPage of @this.Paging.TotalPages</span>
}
</div>
}
// CEC.Blazor/Components/FormControls/PagingControl.razor.cs
public partial class PagingControl<TRecord> :
ComponentBase where TRecord : IDbRecord<TRecord>, new()
{
[CascadingParameter] public IControllerPagingService<TRecord> _Paging { get; set; }
[Parameter] public IControllerPagingService<TRecord> Paging { get; set; }
[Parameter] public PagingDisplayType DisplayType { get; set; } = PagingDisplayType.Full;
[Parameter] public int BlockSize { get; set; } = 0;
private bool IsPaging => this.Paging != null;
private bool IsPagination => this.Paging != null && this.Paging.IsPagination;
protected override Task OnRenderAsync(bool firstRender)
{
if (firstRender)
{
// Check if we have a cascaded IControllerPagingService if so use it
this.Paging = this._Paging ?? this.Paging;
}
if (this.IsPaging)
{
this.Paging.PageHasChanged += this.UpdateUI;
if (this.DisplayType == PagingDisplayType.Narrow) Paging.PagingBlockSize = 4;
if (BlockSize > 0) Paging.PagingBlockSize = this.BlockSize;
}
return base.OnRenderAsync(firstRender);
}
protected async void UpdateUI(object sender, int recordno) => await RenderAsync();
private string IsCurrent(int i) =>
i == this.Paging.CurrentPage ? "active" : string.Empty;
}

WeatherForecastListForm

WeatherForecastListForm的代码很容易解释。OnViewOnEdit路由到查看器或编辑器,或者如果UIOptions指定了使用模态,则打开对话框。

// CEC.Weather/Components/Forms/WeatherForecastListForm.razor.cs
public partial class WeatherForecastListForm :
ListFormBase<DbWeatherForecast, WeatherForecastDbContext>
{
/// The Injected Controller service for this record
[Inject] protected WeatherForecastControllerService ControllerService { get; set; }
protected override Task OnRenderAsync(bool firstRender)
{
if (firstRender)
{
// Sets the specific service
this.Service = this.ControllerService;
// Sets the max column
this.UIOptions.MaxColumn = 3;
}
return base.OnRenderAsync(firstRender);
}
/// Method called when the user clicks on a row in the viewer.
protected void OnView(int id)
{
if (this.UIOptions.UseModalViewer && this.ViewManager.ModalDialog != null)
this.OnModalAsync<WeatherForecastViewerForm>(id);
else this.OnViewAsync<WeatherForecastViewerView>(id);
}
/// Method called when the user clicks on a row Edit button.
protected void OnEdit(int id)
{
if (this.UIOptions.UseModalViewer && this.ViewManager.ModalDialog != null)
this.OnModalAsync<WeatherForecastEditorForm>(id);
else this.OnViewAsync<WeatherForecastEditorView>(id);
}
}

下面的“Razor标记是完整文件的缩写。这充分利用了上一篇文章中介绍的UIControls内容。有关详细信息,请参见注释。

// CEC.Weather/Components/Forms/WeatherForecastListForm.razor
// Wrapper that cascades the values including event handlers
<UIWrapper UIOptions="@this.UIOptions"
RecordConfiguration="@this.Service.RecordConfiguration"
OnView="@OnView" OnEdit="@OnEdit">
// UI CardGrid is a Bootstrap Card
<UICardGrid TRecord="DbWeatherForecast"
IsCollapsible="true" Paging="this.Paging" IsLoading="this.Loading">
<Title>
@this.ListTitle
</Title>
// Header Section of UICardGrid
<TableHeader>
// Header Grid columns
<UIGridTableHeaderColumn TRecord="DbWeatherForecast"
Column="1" FieldName="WeatherForecastID">ID</UIGridTableHeaderColumn>
<UIGridTableHeaderColumn TRecord="DbWeatherForecast"
Column="2" FieldName="Date">Date</UIGridTableHeaderColumn>
.....
<UIGridTableHeaderColumn TRecord="DbWeatherForecast"
Column="7"></UIGridTableHeaderColumn>
</TableHeader>
// Row Template Section of UICardGrid
<RowTemplate>
// Cascaded ID for the specific Row
<CascadingValue Name="RecordID" Value="@context.WeatherForecastID">
<UIGridTableColumn TRecord="DbWeatherForecast"
Column="1">@context.WeatherForecastID</UIGridTableColumn>
<UIGridTableColumn TRecord="DbWeatherForecast"
Column="2">@context.Date.ToShortDateString()</UIGridTableColumn>
.....
<UIGridTableEditColumn TRecord="DbWeatherForecast"></UIGridTableEditColumn>
</CascadingValue>
</RowTemplate>
// Navigation Section of UUCardGrid
<Navigation>
<UIListButtonRow>
// Paging part of UIListButtonRow
<Paging>
<PagingControl TRecord="DbWeatherForecast"
Paging="this.Paging"></PagingControl>
</Paging>
</UIListButtonRow>
</Navigation>
</UICardGrid>
</UIWrapper>

Razor组件使用一组UIControl用于列表构建的。UICardGrid构建Bootstrap卡和表框架。您可以在Github存储库中浏览组件代码。

WeatherForcastListModalView

这很简单。WeatherForecastListFormUIOptions对象的Razor标记。

// CEC.Weather/Components/Views/WeatherForecastListModalView.razor
@using CEC.Blazor.Components
@using CEC.Blazor.Components.BaseForms
@using CEC.Blazor.Components.UIControls
@using CEC.Weather.Data
@using CEC.Weather.Components
@using CEC.Blazor.Components.Base
@namespace CEC.Weather.Components.Views
@inherits Component
@implements IView
<WeatherForecastListForm UIOptions="this.UIOptions"></WeatherForecastListForm>
@code {
[CascadingParameter]
public ViewManager ViewManager { get; set; }
public UIOptions UIOptions => new UIOptions()
{
ListNavigationToViewer = true,
ShowButtons = true,
ShowAdd = true,
ShowEdit = true,
UseModalEditor = true,
UseModalViewer = true
};
}

总结

总结了这篇文章。需要注意的一些关键点:

  1. Blazor服务器和Blazor WASM之间的代码没有差异。
  2. 几乎所有功能都在库组件中实现。大多数应用程序代码都是单个记录字段的Razor标记。
  3. 整个组件和CRUD数据访问都使用了异步功能。

最后

以上就是勤恳大白为你收集整理的在Blazor中构建数据库应用程序——第5部分——查看组件——UI中的CRUD列表操作介绍储存库和数据库列表功能基本表单总结的全部内容,希望文章能够帮你解决在Blazor中构建数据库应用程序——第5部分——查看组件——UI中的CRUD列表操作介绍储存库和数据库列表功能基本表单总结所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(48)

评论列表共有 0 条评论

立即
投稿
返回
顶部