您当前的位置: 首页 >  ui

寒冰屋

暂无认证

  • 0浏览

    0关注

    2286博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

在Blazor中构建数据库应用程序——第5部分——查看组件——UI中的CRUD列表操作

寒冰屋 发布时间:2020-12-12 20:26:46 ,浏览量:0

目录

介绍

储存库和数据库

列表功能

基本表单

分页

初始表单加载

表单事件

页面控件

WeatherForecastListForm

WeatherForcastListModalView

总结

介绍

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

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

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

储存库和数据库

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.PageLoaderDelegateAsync
                           (this.GetDataPageWithSortingAsync);
    // loads the paging object
    await this.LoadAsync();
    // Trigger event so any listeners get notified
    this.TriggerListChangedEvent(this);
}

IControllerPagingService定义一个返回TRecords的List的PageLoaderDelegateAsync委托和一个委托属性PageLoaderAsync。

// CEC.Blazor/Services/Interfaces/IControllerPagingService.cs

// Delegate that returns a generic Record List
public delegate Task 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 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 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。如果Records为null,则仅重新加载。

// CEC.Blazor/Services/BaseControllerService.cs
public async virtual Task 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)
{
    
    @if (this.DisplayType != PagingDisplayType.Narrow) { @if (this.DisplayType == PagingDisplayType.FullwithoutPageSize) {
  • } else {
  • «
  • }
  • Previous
  • @for (int i = this.Paging.StartPage; i @currentpage }
  • Next
  • @if (this.DisplayType == PagingDisplayType.FullwithoutPageSize) {
  • » @this.Paging.TotalPages
  • } else {
  • »
  • } } else {
  • @for (int i = this.Paging.StartPage; i @currentpage }
  • » @this.Paging.TotalPages
  • }
@if (this.DisplayType == PagingDisplayType.Full) { Page @this.Paging.CurrentPage of @this.Paging.TotalPages }
}
// CEC.Blazor/Components/FormControls/PagingControl.razor.cs

public partial class PagingControl : 
       ComponentBase where TRecord : IDbRecord, new()
{
    [CascadingParameter] public IControllerPagingService _Paging { get; set; }

    [Parameter] public IControllerPagingService 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的代码很容易解释。OnView和OnEdit路由到查看器或编辑器,或者如果UIOptions指定了使用模态,则打开对话框。

// CEC.Weather/Components/Forms/WeatherForecastListForm.razor.cs

public partial class WeatherForecastListForm : 
       ListFormBase
{
    /// 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(id);
        else this.OnViewAsync(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(id);
        else this.OnViewAsync(id);
    }
}

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

// CEC.Weather/Components/Forms/WeatherForecastListForm.razor

// Wrapper that cascades the values including event handlers


    // UI CardGrid is a Bootstrap Card
    

        
            @this.ListTitle
        

        // Header Section of UICardGrid
        
            // Header Grid columns
            ID
            Date
            .....
            
        

        // Row Template Section of UICardGrid
        
            // Cascaded ID for the specific Row
            
                @context.WeatherForecastID
                @context.Date.ToShortDateString()
                .....
                
            

        
        // Navigation Section of UUCardGrid
        

            
                // Paging part of UIListButtonRow
                
                    
                
            

        
    

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

WeatherForcastListModalView

这很简单。WeatherForecastListForm和UIOptions对象的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



@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数据访问都使用了异步功能。
关注
打赏
1665926880
查看更多评论
立即登录/注册

微信扫码登录

0.2287s