目录
介绍
这是该系列文章的第四篇,探讨如何在Blazor中构建和构造真正的数据库应用程序。
- 项目结构与框架
- 服务——构建CRUD数据层
- View组件——UI中的CRUD编辑和查看操作
- UI组件——构建HTML / CSS控件
- View组件-UI中的CRUD列表操作
- 逐步详细介绍如何向应用程序添加气象站和气象站数据
本文介绍了我们在UI中使用的组件,然后重点介绍了如何从HTML和CSS构建通用的UI组件。
储存库和数据库
存储库中有一个SQL脚本在/SQL中,用于构建数据库。
您可以在此处查看运行的项目的服务器版本。
你可以看到该项目的WASM版本运行在这里。
组件
Blazor UI中除起始页面之外的所有内容都是组件。是的,路由器,...
应用程序中有四类组件:
- 视图——这些显示在屏幕上。将视图与布局组合在一起以显示窗口。
- 布局——布局与视图结合在一起组成显示窗口。
- 表单——表单是控件的逻辑集合。编辑表单,显示表单,列表表单,数据输入向导都是经典表单。表单包含控件——不包含HTML。
- 控件——控件显示一些内容——发表HTML。文本框、下拉菜单、按钮、网格都是经典控件。
视图
视图是特定于应用程序的,但对于WASM和Server是通用的,因此位于应用程序库的/ Components/Views中。
天气预报视图和列表视图如下所示:
// CEC.Weather/Components/Views/WeatherForecastViewerView.cs
@using CEC.Blazor.Components
@using CEC.Weather.Components
@using CEC.Blazor.Components.Base
@namespace CEC.Weather.Components.Views
@implements IView
@inherits Component
<WeatherForecastViewerForm ID="this.ID"></WeatherForecastViewerForm>
@code {
[CascadingParameter] public ViewManager ViewManager { get; set; }
[Parameter] public int ID { get; set; } = 0;
}
列表视图定义了一个控制各种列表控件显示选项的UIOptions对象。
// CEC.Blazor.Server/Routes/WeatherForecastListView.cs
@using CEC.Blazor.Components
@using CEC.Blazor.Components.UIControls
@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
};
}
表单
表单特定于应用程序,但WASM和Server通用,因此位于应用程序库的/Components/Views中。
下面的代码显示了Weather Viewer。全部是UI控件,没有HTML标记。
// CEC.Weather/Components/Forms/WeatherForecastViewerForm.razor
<UICard>
<Header>
@this.PageTitle
</Header>
<Body>
<UIErrorHandler IsError="this.IsError"
IsLoading="this.IsDataLoading" ErrorMessage="@this.RecordErrorMessage">
<UIContainer>
<UIRow>
<UILabelColumn Columns="2">
Date
</UILabelColumn>
<UIColumn Columns="2">
<FormControlPlainText
Value="@this.Service.Record.Date.AsShortDate()"></FormControlPlainText>
</UIColumn>
<UILabelColumn Columns="2">
ID
</UILabelColumn>
<UIColumn Columns="2">
<FormControlPlainText
Value="@this.Service.Record.ID.ToString()"></FormControlPlainText>
</UIColumn>
<UILabelColumn Columns="2">
Frost
</UILabelColumn>
<UIColumn Columns="2">
<FormControlPlainText
Value="@this.Service.Record.Frost.AsYesNo()"></FormControlPlainText>
</UIColumn>
</UIRow>
..........
</UIContainer>
</UIErrorHandler>
<UIContainer>
<UIRow>
<UIColumn Columns="6">
<UIButton Show="this.IsLoaded" ColourCode="Bootstrap.ColourCode.dark"
ClickEvent="(e => this.NextRecord(-1))">
Previous
</UIButton>
<UIButton Show="this.IsLoaded" ColourCode="Bootstrap.ColourCode.dark"
ClickEvent="(e => this.NextRecord(1))">
Next
</UIButton>
</UIColumn>
<UIButtonColumn Columns="6">
<UIButton Show="!this.IsModal" ColourCode="Bootstrap.ColourCode.nav"
ClickEvent="(e => this.NavigateTo(PageExitType.ExitToList))">
Exit To List
</UIButton>
<UIButton Show="!this.IsModal" ColourCode="Bootstrap.ColourCode.nav"
ClickEvent="(e => this.NavigateTo(PageExitType.ExitToLast))">
Exit
</UIButton>
<UIButton Show="this.IsModal" ColourCode="Bootstrap.ColourCode.nav"
ClickEvent="(e => this.ModalExit())">
Exit
</UIButton>
</UIButtonColumn>
</UIRow>
</UIContainer>
</Body>
页面后面的代码相对简单——复杂性在父类中的样板代码中。它加载记录特定的Controller服务。
// CEC.Weather/Components/Forms/WeatherForecastViewerForm.razor.cs
public partial class WeatherForecastViewerForm : RecordFormBase<DbWeatherForecast,
WeatherForecastDbContext>
{
[Inject]
private WeatherForecastControllerService ControllerService { get; set; }
public override string PageTitle => $"Weather Forecast Viewer
{this.Service?.Record?.Date.AsShortDate() ?? string.Empty}".Trim();
protected override Task OnRenderAsync(bool firstRender)
{
if (firstRender) this.Service = this.ControllerService;
return base.OnRenderAsync(firstRender);
}
}
UI控件
UI控件基于Bootstrap作为UI CSS框架发出HTML和CSS标记。所有控件均继承自ControlBase,UI控件均继承自UIBase。
UIBase
UIBase继承自ControlBase, ControlBase继承自Component。它构建了一个HTML DIV块,您可以打开或关闭它。
让我们详细了解一下UIBase。
可以使用Tag参数设置HTML块标记。但是,组件不会直接使用此属性来构建HTML。它使用了第二个受保护的属性_Tag。在基本实现中,此返回Tag。
/// Css for component - can be overridden and fixed in inherited components
[Parameter]
public virtual string Tag { get; set; } = "div";
protected virtual string _Tag => this.Tag;
_Tag被声明为虚拟的,因此在派生类中,您可以覆盖它并设置标签。因此,在UIAnchor类上,您需要进行设置,然后覆盖Tag中设置的任何内容。
protected overridden string _Tag => "a";
CSS的工作方式与此类似。
/// Css for component - can be overridden and fixed in inherited components
[Parameter]
public virtual string Css { get; set; } = string.Empty;
/// Additional Css to tag on the end of the base Css
[Parameter]
public string AddOnCss { get; set; } = string.Empty;
/// Property for fixing the base Css. Base returns the Parameter Css,
/// but can be overridden in inherited classes
protected virtual string _BaseCss => this.Css;
/// Property for fixing the Add On Css. Base returns the Parameter AddOnCss,
/// but can be overridden say to String.Empty in inherited classes
protected virtual string _AddOnCss => this.AddOnCss;
/// Actual calculated Css string used in the component
protected virtual string _Css => this.CleanUpCss($"{this._BaseCss} {this._AddOnCss}");
/// Method to clean up the Css - remove leading and trailing spaces and any multiple spaces
protected string CleanUpCss(string css)
{
while (css.Contains(" ")) css = css.Replace(" ", " ");
return css.Trim();
}
因此要修复基本的CSS。
protected overridden string _BaseCss => "button";
Razor标记声明的属性在AdditionalAttributes中捕获。UsedAttributes是不添加到组件的列表。ClearDuplicateAttributes()删除UsedAttributes。
/// Gets or sets a collection of additional attributes that will be applied
/// to the created <c>form</c> element.
[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object> AdditionalAttributes { get; set; }
/// Html attributes that need to be removed if set on the control default
/// is only the class attribute
protected List<string> UsedAttributes { get; set; } = new List<string>() { "class" };
/// Method to clean up the Additional Attributes
protected void ClearDuplicateAttributes()
{
if (this.AdditionalAttributes != null && this.UsedAttributes != null)
{
foreach (var item in this.UsedAttributes)
{
if (this.AdditionalAttributes.ContainsKey(item))
this.AdditionalAttributes.Remove(item);
}
}
}
最后,BuildRenderTree为组件构建HTML。在这种情况下,我们将在代码中执行此操作,而不使用Razor标记文件。它
- 检查是否应该显示它。
- 从中清除不需要的AdditionalAttributes属性。
- 用正确的标签创建元素。
- 添加AdditionalAttributes。
- 添加CSS。
- 添加子内容。
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
if (this._Show)
{
this.ClearDuplicateAttributes();
builder.OpenElement(0, this._Tag);
builder.AddMultipleAttributes(1, AdditionalAttributes);
builder.AddAttribute(2, "class", this._Css);
if (!string.IsNullOrEmpty(this._Content))
builder.AddContent(3, (MarkupString)this._Content);
else if (this.ChildContent != null) builder.AddContent(4, ChildContent);
builder.CloseElement();
}
}
UIBootstrapBase
UIBootstrapBase为Bootstrap组件添加了额外的功能。格式选项(例如,组件颜色和大小调整)表示为Enum,并且根据选定的Enum来构建Css片段。
// CEC.Blazor/Components/UIControls/UIBootstrapBase.cs
public class UIBootstrapBase : UIBase
{
protected virtual string CssName { get; set; } = string.Empty;
/// Bootstrap Colour for the Component
[Parameter]
public Bootstrap.ColourCode ColourCode { get; set; } = Bootstrap.ColourCode.info;
/// Bootstrap Size for the Component
[Parameter]
public Bootstrap.SizeCode SizeCode { get; set; } = Bootstrap.SizeCode.normal;
/// Property to set the HTML value if appropriate
[Parameter]
public string Value { get; set; } = "";
/// Property to get the Colour CSS
protected virtual string ColourCssFragment => GetCssFragment<Bootstrap.ColourCode>
(this.ColourCode);
/// Property to get the Size CSS
protected virtual string SizeCssFragment => GetCssFragment<Bootstrap.SizeCode>
(this.SizeCode);
/// CSS override
protected override string _Css => this.CleanUpCss($"{this.CssName}
{this.SizeCssFragment} {this.ColourCssFragment} {this.AddOnCss}");
/// Method to format as Bootstrap CSS Fragment
protected string GetCssFragment<T>
(T code) => $"{this.CssName}-{Enum.GetName(typeof(T), code).Replace("_", "-")}";
}
一些例子
本文的其余部分将更详细地介绍一些UI控件。
UIButton
这是一个标准的Bootstrap按钮。
- ButtonType和ClickEvent特定于按钮的。
- CssName和_Tag硬连线。
- ButtonClick 处理按钮单击事件。
- BuildRenderTree构建标记并连接JSInterop onclick事件。
- Show 控制是否渲染按钮。
// CEC.Blazor/Components/UIControls/UIButton.cs
public class UIButton : UIBootstrapBase
{
/// Property setting the button HTML attribute Type
[Parameter]
public string ButtonType { get; set; } = "button";
/// Override the CssName
protected override string CssName => "btn";
/// Override the Tag
protected override string _Tag => "button";
/// Callback for a button click event
[Parameter]
public EventCallback<MouseEventArgs> ClickEvent { get; set; }
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
if (this.Show)
{
builder.OpenElement(0, this._Tag);
builder.AddAttribute(1, "type", this.ButtonType);
builder.AddAttribute(2, "class", this._Css);
builder.AddAttribute(3, "onclick",
EventCallback.Factory.Create<MouseEventArgs>(this, this.ButtonClick));
builder.AddContent(4, ChildContent);
builder.CloseElement();
}
}
/// Event handler for button click
protected void ButtonClick(MouseEventArgs e) => this.ClickEvent.InvokeAsync(e);
}
这是一些代码,显示了正在使用的控件。
// CEC.Weather/Components/Forms/WeatherViewer.razor
<UIButtonColumn Columns="6">
<UIButton Show="!this.IsModal" ColourCode="Bootstrap.ColourCode.nav"
ClickEvent="(e => this.NavigateTo(PageExitType.ExitToList))">
Exit To List
</UIButton>
<UIButton Show="!this.IsModal" ColourCode="Bootstrap.ColourCode.nav"
ClickEvent="(e => this.NavigateTo(PageExitType.ExitToLast))">
Exit
</UIButton>
<UIButton Show="this.IsModal" ColourCode="Bootstrap.ColourCode.nav"
ClickEvent="(e => this.ModalExit())">
Exit
</UIButton>
</UIButtonColumn>
UIAlert
这是标准的Bootstrap警报。
- Alert是封装的Alert类。
- ColourCssFragement,Show和_Content连接到Alert对象实例中。
// CEC.Blazor/Components/UIControls/UI/UIAlert.cs
public class UIAlert : UIBootstrapBase
{
/// Alert to display
[Parameter]
public Alert Alert { get; set; } = new Alert();
/// Set the CssName
protected override string CssName => "alert";
/// Property to override the colour CSS
protected override string ColourCssFragment => this.Alert != null ?
GetCssFragment<Bootstrap.ColourCode>(this.Alert.ColourCode) :
GetCssFragment<Bootstrap.ColourCode>(this.ColourCode);
/// Boolean Show override
protected override bool _Show => this.Alert?.IsAlert ?? false;
/// Override the content with the alert message
protected override string _Content => this.Alert?.Message ?? string.Empty;
}
这是一些代码,显示了正在使用的控件。
// CEC.Weather/Components/Forms/WeatherEditor.razor
<UIContainer>
<UIRow>
<UIColumn Columns="7">
<UIAlert Alert="this.AlertMessage"
SizeCode="Bootstrap.SizeCode.sm"></UIAlert>
</UIColumn>
<UIButtonColumn Columns="5">
.........
</UIButtonColumn>
</UIRow>
</UIContainer>
UIErrorHandler
这是一个包装控件,旨在保存子内容中的实现错误检查。它具有由IsError和IsLoading控制的三个状态:
- 加载中——显示加载消息和微调器时
- 错误——当它显示错误消息时
- 已加载——显示子内容时
子内容中的所有控件仅在加载完成且IsError为false时才添加到RenderTree。
该控件节省了在子内容中实现大量错误检查的过程。
// CEC.Blazor/Components/UIControls/UI/UIErrorHandler.cs
public class UIErrorHandler : UIBase
{
/// Enum for the Control State
public enum ControlState { Error = 0, Loading = 1, Loaded = 2}
/// Boolean Property that determines if the child content or an error message is diplayed
[Parameter]
public bool IsError { get; set; } = false;
/// Boolean Property that determines if the child content or an loading message is diplayed
[Parameter]
public bool IsLoading { get; set; } = true;
/// Control State
public ControlState State
{
get
{
if (IsError && !IsLoading) return ControlState.Error;
else if (!IsLoading) return ControlState.Loaded;
else return ControlState.Loading;
}
}
/// CSS Override
protected override string _BaseCss => this.IsLoading?
"text-center p-3": "label label-error m-2";
/// Customer error message to display
[Parameter]
public string ErrorMessage { get; set; } = "An error has occured loading the content";
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
this.ClearDuplicateAttributes();
switch (this.State)
{
case ControlState.Loading:
builder.OpenElement(1, "div");
builder.AddAttribute(2, "class", this._Css);
builder.OpenElement(3, "button");
builder.AddAttribute(4, "class", "btn btn-primary");
builder.AddAttribute(5, "type", "button");
builder.AddAttribute(6, "disabled", "disabled");
builder.OpenElement(7, "span");
builder.AddAttribute(8, "class", "spinner-border spinner-border-sm pr-2");
builder.AddAttribute(9, "role", "status");
builder.AddAttribute(10, "aria-hidden", "true");
builder.CloseElement();
builder.AddContent(11, " Loading...");
builder.CloseElement();
builder.CloseElement();
break;
case ControlState.Error:
builder.OpenElement(1, "div");
builder.OpenElement(2, "span");
builder.AddAttribute(3, "class", this._Css);
builder.AddContent(4, ErrorMessage);
builder.CloseElement();
builder.CloseElement();
break;
default:
builder.AddContent(1, ChildContent);
break;
};
}
}
这是一些代码,显示了正在使用的控件。
// CEC.Weather/Components/Forms/WeatherViewer.razor
<UICard>
<Header>
@this.PageTitle
</Header>
<Body>
<UIErrorHandler IsError="this.IsError"
IsLoading="this.IsDataLoading" ErrorMessage="@this.RecordErrorMessage">
<UIContainer>
..........
</UIContainer>
</UIErrorHandler>
.......
</Body>
UIContainer/UIRow/UIColumn
这些控件通过使用正确的Css构建DIV来创建BootStrap网格系统(即容器、行和列)。
// CEC.Blazor/Components/UIControls/UIBootstrapContainer/UIContainer.cs
public class UIContainer : UIBase
{
// Overrides the _BaseCss property to force the css_
protected override string _BaseCss => "container-fluid";
}
// CEC.Blazor/Components/UIControls/UIBootstrapContainer/UIRow.cs
public class UIRow : UIBase
{
protected override string _BaseCss => "row";
}
// CEC.Blazor/Components/UIControls/UIBootstrapContainer/UIColumn.cs
public class UIColumn : UIBase
{
[Parameter]
public int Columns { get; set; } = 1;
protected override string _BaseCss => $"col-{Columns}";
}
// CEC.Blazor/Components/UIControls/UIBootstrapContainer/UILabelColumn.cs
public class UILabelColumn : UIColumn
{
protected override string _BaseCss => $"col-{Columns} col-form-label";
}
这是一些代码,显示了正在使用的控件。
// CEC.Weather/Components/Forms/WeatherViewer.razor
<UIContainer>
<UIRow>
<UILabelColumn Columns="2">
Date
</UILabelColumn>
............
</UIRow>
..........
</UIContainer>
总结
本文概述了如何使用组件构建UI控件,并更详细地研究了一些示例组件。您可以在GitHub Repository- CEC.Blazor/Components/UIControls中看到所有库UIControls。
需要注意的一些关键点:
- UI控件使您可以从高级组件(例如,窗体和视图)中抽象标记。
- UI控件使您可以对HTML和CSS标记进行控制,并为盟友提供一些纪律。
- 您的主要“视图”和“表单”组件更干净,更易于查看。
- 您可以根据需要使用尽可能少的抽象。
- 诸如UIErrorHandler的控件使生活更轻松!