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

目录

介绍

储存库和数据库

组件

视图

表单

UI控件

UIBase

UIBootstrapBase

一些例子

UIButton

UIAlert

UIErrorHandler

UIContainer/UIRow/UIColumn

总结


介绍

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

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

本文介绍了我们在UI中使用的组件,然后重点介绍了如何从HTMLCSS构建通用的UI组件。

储存库和数据库

CEC.Blazor GitHub存储库

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

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

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

组件

Blazor UI中除起始页面之外的所有内容都是组件。是的,路由器,...

应用程序中有四类组件:

  1. 视图——这些显示在屏幕上。将视图与布局组合在一起以显示窗口。
  2. 布局——布局与视图结合在一起组成显示窗口。
  3. 表单——表单是控件的逻辑集合。编辑表单,显示表单,列表表单,数据输入向导都是经典表单。表单包含控件——不包含HTML
  4. 控件——控件显示一些内容——发表HTML。文本框、下拉菜单、按钮、网格都是经典控件。

视图

视图是特定于应用程序的,但对于WASMServer是通用的,因此位于应用程序库的/ 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
    };
}

表单

表单特定于应用程序,但WASMServer通用,因此位于应用程序库的/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框架发出HTMLCSS标记。所有控件均继承自ControlBaseUI控件均继承自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标记文件。

  1. 检查是否应该显示它。
  2. 从中清除不需要的AdditionalAttributes属性。
  3. 用正确的标签创建元素。
  4. 添加AdditionalAttributes
  5. 添加CSS
  6. 添加子内容。
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

UIBootstrapBaseBootstrap组件添加了额外的功能。格式选项(例如,组件颜色和大小调整)表示为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按钮。

  1. ButtonTypeClickEvent特定于按钮的。
  2. CssName_Tag硬连线。
  3. ButtonClick 处理按钮单击事件。
  4. BuildRenderTree构建标记并连接JSInterop onclick事件。
  5. 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警报。

  1. Alert是封装的Alert类。
  2. ColourCssFragementShow_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

这是一个包装控件,旨在保存子内容中的实现错误检查。它具有由IsErrorIsLoading控制的三个状态:

  1. 加载中——显示加载消息和微调器时
  2. 错误——当它显示错误消息时
  3. 已加载——显示子内容时

子内容中的所有控件仅在加载完成且IsErrorfalse时才添加到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

需要注意的一些关键点:

  1. UI控件使您可以从高级组件(例如,窗体和视图)中抽象标记。
  2. UI控件使您可以对HTMLCSS标记进行控制,并为盟友提供一些纪律。
  3. 您的主要视图表单组件更干净,更易于查看。
  4. 您可以根据需要使用尽可能少的抽象。
  5. 诸如UIErrorHandler的控件使生活更轻松!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值