目录

介绍

示例项目、代码和链接

基本表单

RecordFormBase

EditRecordFormBase

实现表单

WeatherForecastViewerForm

WeatherForecastEditorForm

RouteView实现

总结


介绍

这是关于如何在Blazor中构建和构建数据库应用程序的系列文章中的第三篇。到目前为止的文章是:

  1. 项目结构和框架——一些介绍。
  2. 服务——构建CRUD数据层。
  3. 查看组件——UI中的CRUD编辑和查看操作。
  4. UI组件——构建 HTML/CSS控件。
  5. 查看组件——UI中的CRUD列表操作。

本文详细介绍了构建可重用的CRUD表示层组件,特别是编辑和查看功能。自第一个版本以来发生了重大变化。

我发现有趣的是,大多数程序员尝试通过构建控件生成器而不是样板程序来自动化编辑和查看表单。大多数表单对于它们的记录集都是独一无二的。某些字段可以组合在一起并放在同一行上。文本字段的长度会根据需要的字符数而变化。建立一个工厂来处理这个问题,再加上控件、数据类实例和验证之间的链接的额外复杂性,似乎不值得。配置数据集变得比它试图模仿的形式更复杂。由于这些原因,这里没有表单构建器,只有一组用于标准化表单构建的库UI组件类。

示例项目、代码和链接

文章的存储库已移至CEC.Database存储库。您可以将其用作开发您自己的应用程序的模板。以前的存储库已过时,将被删除。

存储库中的/SQL中有一个用于构建数据库的SQL脚本。该应用程序可以使用真正的SQL数据库或内存中的SQLite数据库。

您可以在同一站点上看到在此处运行的项目的Server和WASM版本。

表单中使用了几个自定义控件。有关这些的详细信息在单独的文章中进行了介绍:

  • 编辑窗体状态控件
  • 编辑验证状态控件
  • 内联对话框控件
  • 模态对话框控件

基本表单

所有UI组件都继承自ComponentBase。所有源文件都可以在Github站点上查看,我在文章的适当位置包含了特定代码文件的引用或链接。在大多数地方,您需要通读代码以获取对功能的详细评论。

RecordFormBase

RecordFormBase是记录表单使用的基本抽象类。它继承自ComponentBase.。可以在多种上下文中创建记录表单:

  1. 作为RouteView中的根组件,RouteView通过参数Id传递表单。
  2. 在列表或其他组件内的模式对话框中。ID通过DialogOptions类传递给表单。
  3. 作为另一个组件(例如列表)中的内联编辑器,其中组件通过Id参数传递表单。

RecordFormBase旨在通过检查级联IModalDialog对象来检测它的上下文,特别是模态对话框上下文。有两组依赖:

  1. 记录的Id。如果表单是在RouteView或其他部件中托管的,它可以作为Parameter传递,或在IModalDialog的公共托管ModalOptions属性中传递。请注意,Id为-1表示新记录,而为0表示默认记录(0 是int的默认值)。
  2. 退出机制。这要么是:
  3. 如果它在模态上下文中,则调用Modal的close 。
  4. 如果已注册,则调用ExitAction委托。
  5. 默认——退出到root。
// Blazor.SPA/Components/Base/RecordFormBase.cspublic class RecordFormBase<TRecord> : ComponentBase  where TRecord : class, IDbRecord<TRecord>, new(){[CascadingParameter] public IModalDialog Modal { get; set; }[Parameter] public int ID {get; set;}[Parameter] public EventCallback ExitAction { get; set; }[Inject] protected NavigationManager NavManager { get; set; }protected IFactoryControllerService<TRecord> Service { get; set; }protected virtual bool IsLoaded => this.Service != null && this.Service.Record != null;protected virtual bool HasServices => this.Service != null;protected bool _isModal => this.Modal != null;protected int _modalId = 0;protected int _Id => _modalId != 0 ? _modalId : this.ID;protected async override Task OnInitializedAsync(){await LoadRecordAsync();await base.OnInitializedAsync();}protected virtual async Task LoadRecordAsync(){this.TryGetModalID();await this.Service.GetRecordAsync(this._Id);}protected virtual bool TryGetModalID(){if (this._isModal && this.Modal.Options.TryGet<int>("Id", out int value)){this._modalId = value;return true;}return false;}protected virtual void Exit(){if (this._isModal)this.Modal.Close(ModalResult.OK());else if (ExitAction.HasDelegate)ExitAction.InvokeAsync();elsethis.NavManager.NavigateTo("/");}}

EditRecordFormBase

EditRecordFormBase是编辑器表单的基础。它继承自RecordFormBase并实现了编辑功能。

它:

  1. 管理EditContext.
  2. 有一组布尔属性来跟踪状态和管理按钮显示/禁用状态。
  3. 保存记录。

当表单脏时,该Dirty属性与模态对话框连接以将其锁定(不允许退出和关闭导航)。

// Blazor.SPA/Components/Base/EditRecordFormBase.cs
public abstract class EditRecordFormBase<TRecord> : RecordFormBase<TRecord>, IDisposable where TRecord : class, IDbRecord<TRecord>, new()
{/// Edit Context for the Editor - built from the service recordprotected EditContext EditContext { get; set; }/// Property tracking the Edit state of the formprotected bool IsDirty{get => this._isDirty;set{if (value != this.IsDirty){this._isDirty = value;if (this._isModal) this.Modal.Lock(value);}}}/// model used by the Edit Contextprotected TRecord Model => this.Service?.Record ?? null;/// Reference to the form EditContextState controlprotected EditFormState EditFormState { get; set; }

下一组属性是代码中使用的状态属性,以及用于控制显示/禁用状态的razor按钮。

protected bool _isNew => this.Service?.IsNewRecord ?? true;
protected bool _isDirty = false;
protected bool _isValid = true;
protected bool _saveDisabled => !this.IsDirty || !this._isValid;
protected bool _deleteDisabled => this._isNew || this._confirmDelete;
protected bool _isLoaded = false;
protected bool _dirtyExit = false;
protected bool _confirmDelete = false;
protected bool _isInlineDirty => (!this._isModal) && this._isDirty;
protected string _saveButtonText => this._isNew ? "Save" : "Update";

LoadRecordAsync调用base以获取记录,创建EditContext并注册EditContext.OnFieldChanged.。其他方法处理状态变化。

protected async override Task OnInitializedAsync()=> await LoadRecordAsync();/// Method to load the record
/// calls the base method to load the record and then sets up the EditContext
protected override async Task LoadRecordAsync()
{await base.OnInitializedAsync();this.EditContext = new EditContext(this.Model);_isLoaded = true;this.EditContext.OnFieldChanged += FieldChanged;if (!this._isNew)this.EditContext.Validate();
}/// Event handler for EditContext OnFieldChanged Event
protected void FieldChanged(object sender, FieldChangedEventArgs e)
{this._dirtyExit = false;this._confirmDelete = false;
}/// Method to change edit state
protected void EditStateChanged(bool dirty)=> this.IsDirty = dirty;/// Method to change the Validation state
protected void ValidStateChanged(bool valid)=> this._isValid = valid;/// IDisposable Interface Implementation
public void Dispose()=> this.EditContext.OnFieldChanged -= FieldChanged;

最后是按钮事件处理程序来控制保存和退出脏表单。

   /// Method to handle EditForm submissionprotected async void HandleValidSubmit(){await this.Service.SaveRecordAsync();this.EditFormState.UpdateState();this._dirtyExit = false;await this.InvokeAsync(this.StateHasChanged);}/// Handler for Delete actionprotected void Delete(){if (!this._isNew)this._confirmDelete = true;}/// Handler for Delete confirmationprotected async void ConfirmDelete(){if (this._confirmDelete){await this.Service.DeleteRecordAsync();this.IsDirty = false;this.DoExit();}}/// Handler for a confirmed exit - i.e.  dirty exitprotected void ConfirmExit(){this.IsDirty = false;this.DoExit();}/// Handler to Exit the form, dependant on it contextprotected void DoExit(ModalResult result = null){result = result ?? ModalResult.OK();if (this._isModal)this.Modal.Close(result);if (ExitAction.HasDelegate)ExitAction.InvokeAsync();elsethis.NavManager.NavigateTo("/");}
}

实现表单

WeatherForecastViewerForm

WeatherForecastViewerForm的代码非常简单。

  1. 继承自RecordFormBase并设置TRecord为WeatherForecast.
  2. 获取WeatherForecastControllerService并将其分配给基本Service属性。
public partial class WeatherForecastViewerForm : RecordFormBase<WeatherForecast>
{[Inject] private WeatherForecastControllerService ControllerService { get; set; }protected async override Task OnInitializedAsync(){this.Service = this.ControllerService;await base.OnInitializedAsync();}
}

大部分工作都在Razor代码中。

  1. 没有Html代码,都是组件。我们将在下一篇文章中详细介绍UI组件。
  2. 布局基于Bootstrap网格。
  3. 列大小决定控件大小。
  4. 仅当我们有要显示的记录时才加载UILoader 的内容。
@namespace Blazor.Database.Components
@inherits RecordFormBase<WeatherForecast><UIContainer><UIFormRow><UIColumn><h2>Weather Forecast Viewer</h2></UIColumn></UIFormRow>
</UIContainer>
<UILoader Loaded="this.IsLoaded"><UIContainer><UIFormRow><UILabelColumn>Date</UILabelColumn><UIInputColumn Cols="3"><InputReadOnlyText Value="@this.ControllerService.Record.Date.ToShortDateString()"></InputReadOnlyText></UIInputColumn><UIColumn Cols="7"></UIColumn></UIFormRow><UIFormRow><UILabelColumn>Temperature °C</UILabelColumn><UIInputColumn Cols="2"><InputReadOnlyText Value="@this.ControllerService.Record.TemperatureC.ToString()"></InputReadOnlyText></UIInputColumn><UIColumn Cols="8"></UIColumn></UIFormRow><UIFormRow><UILabelColumn>Temperature °f</UILabelColumn><UIInputColumn Cols="2"><InputReadOnlyText Value="@this.ControllerService.Record.TemperatureF.ToString()"></InputReadOnlyText></UIInputColumn><UIColumn Cols="8"></UIColumn></UIFormRow><UIFormRow><UILabelColumn>Summary</UILabelColumn><UIInputColumn Cols="9"><InputReadOnlyText Value="@this.ControllerService.Record.Summary"></InputReadOnlyText></UIInputColumn></UIFormRow></UIContainer>
</UILoader>
<UIContainer><UIFormRow><UIButtonColumn><UIButton AdditionalClasses="btn-secondary" ClickEvent="this.Exit">Exit</UIButton></UIButtonColumn></UIFormRow>
</UIContainer>

WeatherForecastEditorForm

WeatherForecastEditorForm类似于WeatherForecastViewerForm。

代码再次非常简单。

  1. 继承自EditRecordFormBase并设置TRecord为WeatherForecast.
  2. 获取WeatherForecastControllerService并将其分配给基本Service属性。
public partial class WeatherForecastViewerForm : RecordFormBase<WeatherForecast>
{[Inject] private WeatherForecastControllerService ControllerService { get; set; }protected async override Task OnInitializedAsync(){this.Service = this.ControllerService;await base.OnInitializedAsync();}
}

Razor文件如下所示。它基于带有一些附加控件的标准Blazor EditForm。在查看器上发表的相同评论也适用于此处。此外:

  1. InlineDialog是一个表单锁定控件。它由_isInlineDirty属性启用。转到演示站点并编辑记录以查​​看其实际效果。它仅在表单不在模态上下文中时启用。
  2. EditFormState是一个跟踪表单状态的控件,即当表单加载时记录与原始记录的状态。它链接InlineDialog到控制表单锁定。
  3. ValidationFormState 是一个自定义验证控件。
  4. 按钮与布尔控件属性相关联以管理其状态。

自定义控件在链接部分中引用的单独文章中进行了介绍。我没有进一步抽象,所以你可以看到一个完整的表格。您可以将所有标题、编辑表单和按钮部分移动到FormWrapper组件中。

@namespace Blazor.Database.Components
@inherits EditRecordFormBase<WeatherForecast><InlineDialog Lock="this._isInlineDirty" Transparent="false"><UIContainer><UIFormRow><UIColumn><h2>Weather Forecast Editor</h2></UIColumn></UIFormRow></UIContainer><UILoader Loaded="this._isLoaded"><EditForm EditContext="this.EditContext" OnValidSubmit="HandleValidSubmit" class=" px-2 py-3"><EditFormState @ref="this.EditFormState"  EditStateChanged="this.EditStateChanged"></EditFormState><ValidationFormState ValidStateChanged="this.ValidStateChanged"></ValidationFormState><UIContainer><UIFormRow><UILabelColumn>Record ID</UILabelColumn><UIInputColumn Cols="3"><InputReadOnlyText Value="@this.Model.ID.ToString()" /></UIInputColumn><UIColumn Cols="3"></UIColumn><UIValidationColumn><ValidationMessage For=@(() => this.Model.Date) /></UIValidationColumn></UIFormRow><UIFormRow><UILabelColumn>Date</UILabelColumn><UIInputColumn Cols="3"><InputDate class="form-control" @bind-Value="this.Model.Date"></InputDate></UIInputColumn><UIColumn Cols="3"></UIColumn><UIValidationColumn><ValidationMessage For=@(() => this.Model.Date) /></UIValidationColumn></UIFormRow><UIFormRow><UILabelColumn>Temperature °C</UILabelColumn><UIInputColumn Cols="2"><InputNumber class="form-control" @bind-Value="this.Model.TemperatureC"></InputNumber></UIInputColumn><UIColumn Cols="4"></UIColumn><UIValidationColumn><ValidationMessage For=@(() => this.Model.TemperatureC) /></UIValidationColumn></UIFormRow><UIFormRow><UILabelColumn>Summary</UILabelColumn><UIInputColumn><InputText class="form-control" @bind-Value="this.Model.Summary"></InputText></UIInputColumn><UIValidationColumn><ValidationMessage For=@(() => this.Model.Summary) /></UIValidationColumn></UIFormRow></UIContainer><UIContainer><UIFormRow><UIButtonColumn><UIButton Show="true" Disabled="this._deleteDisabled" AdditionalClasses="btn-outline-danger" ClickEvent="() => Delete()">Delete</UIButton><UIButton Show="this._confirmDelete" AdditionalClasses="btn-danger" ClickEvent="() => this.ConfirmDelete()">Confirm Delete</UIButton><UIButton Show="true" Disabled="this._saveDisabled" Type="submit" AdditionalClasses="btn-success">@this._saveButtonText</UIButton><UIButton Show="this._dirtyExit" AdditionalClasses="btn-danger" ClickEvent="() => this.ConfirmExit()">Exit Without Saving</UIButton><UIButton Show="true" Disabled="this._dirtyExit" AdditionalClasses="btn-dark" ClickEvent="() => this.Exit()">Exit</UIButton></UIButtonColumn></UIFormRow></UIContainer></EditForm></UILoader>
</InlineDialog>

RouteView实现

查看器的RouteView实现如下所示。

  1. 用ID Parameter声明Route。
  2. 声明表单WeatherForecastViewerForm。
  3. 将 ID传递给表单并附加一个委托ExitAction,该委托将返回到fetchdata视图。
// WeatherViewer.razor
@page "/weather/view/{ID:int}"<WeatherForecastViewerForm ID="this.ID" ExitAction="this.ExitToList"></WeatherForecastViewerForm>@code {[Parameter] public int ID { get; set; }[Inject] public NavigationManager NavManager { get; set; }private void ExitToList()=> this.NavManager.NavigateTo("/fetchdata");}

编辑器完全相同,但声明了表单WeatherForecastEditorForm。

// WeatherEditor.razor
@page "/weather/edit/{ID:int}"<WeatherForecastEditorForm ID="this.ID" ExitAction="this.ExitToList"></WeatherForecastEditorForm>@code {[Inject] public NavigationManager NavManager { get; set; }[Parameter] public int ID { get; set; }private void ExitToList()=> this.NavManager.NavigateTo("/fetchdata");
}

总结

这篇文章到此结束。我们已经展示了如何将样板代码构建到基本表单中以及如何实现查看器和编辑器表单。我们将在单独的文章中更详细地介绍列表形式以及如何调用查看器和编辑器。

需要注意的一些关键点:

  1. Blazor服务器和Blazor WASM代码是相同的。
  2. 几乎所有功能都在库组件中实现。大多数应用程序代码是各个记录字段的Razor标记。
  3. Razor文件包含控件,而不是HTML。
  4. 通过使用异步。

如果您在未来阅读本文,请查看存储库中的自述文件以获取文章集的最新版本。

Building a Database Application in Blazor - Part 3 - CRUD Edit and View Operations in the UI - CodeProject

在Blazor中构建数据库应用程序——第3部分——UI中的CRUD编辑和查看操作相关推荐

  1. 在Blazor中构建数据库应用程序——第4部分——UI控件

    目录 介绍 存储库和数据库 组件 RouteViews 表单 UI控件 UIBase 一些例子 UIButton UIColumn UILoader UIContainer/UIRow/UIColum ...

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

    目录 介绍 存储库和数据库 列表功能 基本表单 分页和排序 分页器 分页器数据 分页器控件 排序控件 UIDataTableHeaderColumn 天气预报列表表单 视图 WeatherForeca ...

  3. 在Blazor中构建数据库应用程序——第2部分——服务——构建CRUD数据层

    目录 存储库和数据库 目标 服务 泛型 数据访问 DbTaskResult 数据类 WeatherForecast 实体框架层 WeatherForecastDBContext LocalWeathe ...

  4. 在Blazor中构建数据库应用程序——第1部分——项目结构和框架

    目录 介绍 存储库和数据库 设计理念 数据 UI 解决方案结构 界面结构 页面 路由视图 布局 表单 控件 Blazor.Database项目 Program.cs ServiceCollection ...

  5. 在Blazor中构建数据库应用程序——第6部分——向天气应用程序添加新记录类型及其UI

    目录 介绍 示例项目和代码 过程概述 数据库 CEC天气库 为记录添加模型类 添加一些实用程序类 更新WeatherForecastDbContext 添加数据和控制器服务 表单 WeatherSta ...

  6. 数据库应用程序开发基础篇—— .NET中SQL Server数据库的操作C#篇之一

    数据库应用程序开发基础篇-- .NET中SQL Server数据库的操作C#篇之一 写在前面:前面介绍了数据库系统的基本概念,SQl语句基本使用方法,接下来通过学习具体语言和具体数据库结合的应用开发来 ...

  7. 浅析C#中构建多线程应用程序

    *************************************************** 更多精彩,欢迎进入:http://shop115376623.taobao.com ****** ...

  8. c 语言如何处理表格文件中的数据库,C#程序从Excel表格中读取数据并进行处理

    今天做了一个Excel表格数据处理的事情,因为数据量表较大(接近7000条)所以处理起来有点麻烦,于是写了一个程序, 先将程序记下以便将来查找. using System; using System. ...

  9. PHP laravel 加模块,php – 如何在Laravel 5中构建模块化应用程序?

    我想在模块中划分我的应用程序.例如,将有一个"核心"模块,其中包含基本登录功能,应用程序布局/格式化(CSS等),用户管理和日记. 稍后我可以创建其他模块,如联系人管理器,可以轻松 ...

最新文章

  1. grpc框架_分布式RPC框架dubbo、motan、rpcx、gRPC、thrift简介与性能比较
  2. acid四大特性_深入理解MySQL的ACID四大特性原理
  3. Ext 入门 (05) 打印+gridpanel()方法
  4. Visual Studio常用的快捷键整理
  5. 第7章 SQL查询优化
  6. c++ h264RTP接收和发送程序
  7. 托福试卷真题_历年托福考试阅读真题汇总含答案
  8. 如何在Vue项目中使用vw实现移动端适配
  9. 关于领域模型与技术架构的关系的思考
  10. HTTPD虚拟主机配置
  11. Redis——SDS
  12. ARTS1(Algorithm, Review , Tip/Techni, Share)
  13. NASA HEG tool安装心得
  14. 创建API Signing Key
  15. 【css】渐变-背景渐变、边框渐变、文字渐变
  16. 2019值得购买的运动蓝牙耳机推荐
  17. DHT11大气温湿度模块+直流电机实现根据温度控制风扇转动
  18. 金融行业案例 | 未来智安XDR助力银行业客户优化安全运营体系,有效提高告警研判率
  19. ubuntu 扩充交换空间
  20. 牛顿迭代法求一个数的平方根(python)

热门文章

  1. ajax 清洗剂,Choice浴室清洁剂大测评!最好用的只要$3.5!这些产品最好别买...
  2. 苹果safari关掉java_如何在Safari,Chrome,Firefox和系统范围内禁用Java | MOS86
  3. 深入java虚拟机需要读吗_《深入理解Java虚拟机》读后总结(一)JVM内存模型
  4. com 组件调用不起来_Spring Cloud Alibaba,分布式服务调用(四)
  5. tomcat命令linux,Linux下一些操作Tomcat的命令
  6. python读取doc文件 语义识别_我可以让socket.makefile具有与常规文件相同的读取语义吗?...
  7. 设计灵感|音乐播放器App界面如何设计?
  8. 游戏桌面壁纸|英雄联盟,热爱游戏的朋友看这里
  9. 在集设浏览学习高水准海报设计,会带给你不一样的灵感!
  10. 色彩艺术海报PSD模板 | 用色彩挑动你的情趣