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

目录

介绍

示例项目和代码

过程概述

数据库

CEC天气库

为记录添加模型类

添加一些实用程序类

更新WeatherForecastDbContext

添加数据和控制器服务

表单

WeatherStation查看器表单

WeatherStation编辑器表单

WeatherStation列表表单

天气报告表单

导航菜单

过滤器控件

CEC.Blazor.Server

Startup.cs

气象站路由/视图

CEC.Blazor.WASM.Client

program.cs

气象站路由/视图

CEC.Blazor.WASM.Server

Startup.cs

气象站控制器

总结


介绍

这是该系列的第六篇文章,并逐步向Weather Application中添加新记录。

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

该练习的目的是从英国气象局导入站数据。解决方案中包含一个命令行导入程序项目,用于获取和导入数据——查看代码以查看其工作方式。数据采用的是英国气象站自1928年以来的月度记录形式。我们将添加两种记录类型:

  • 气象站
  • 气象站报告

并且所有基础结构都为这两个记录提供UI CRUD操作。

在构建服务器和WASM部署时,我们有4个项目要添加代码:

  1. CEC.Weather ——共享项目库
  2. CEC.Blazor.Server ——服务器项目
  3. CEC.Blazor.WASM.Client ——WASM项目
  4. CEC.Blazor.WASM.Server ——WASM项目的API服务器

大部分代码是CEC.Weather中的库代码。

示例项目和代码

基本代码在CEC.Blazor GitHub存储库中

本文的完整代码位于CEC.Weather GitHub Repository

过程概述

  1. 将表、视图和存储过程添加到数据库
  2. 将模型、服务和表单添加到CEC.Weather库中
  3. 添加视图并在Blazor.CEC.Server项目中配置服务。
  4. 添加视图并在Blazor.CEC.WASM.Client项目中配置服务。
  5. 添加控制器并在Blazor.CEC.WASM.Server项目中配置服务。

数据库

将每种记录类型的表添加到数据库。

CREATE TABLE [dbo].[WeatherStation](
	[WeatherStationID] [int] IDENTITY(1,1) NOT NULL,
	[Name] <a href="50">varchar</a> NOT NULL,
	[Latitude] <a href="8, 4">decimal</a> NOT NULL,
	[Longitude] <a href="8, 4">decimal</a> NOT NULL,
	[Elevation] <a href="8, 2">decimal</a> NOT NULL
)
CREATE TABLE [dbo].[WeatherReport](
	[WeatherReportID] [int] IDENTITY(1,1) NOT NULL,
	[WeatherStationID] [int] NOT NULL,
	[Date] [smalldatetime] NULL,
	[TempMax] <a href="8, 4">decimal</a> NULL,
	[TempMin] <a href="8, 4">decimal</a> NULL,
	[FrostDays] [int] NULL,
	[Rainfall] <a href="8, 4">decimal</a> NULL,
	[SunHours] <a href="8, 2">decimal</a> NULL
)

为每种记录类型添加视图。

注意

  1. 它们既映射ID又映射DisplayNameIDbRecord
  2. WeatherReport映射MonthYear以允许SQL Server在这些字段上进行过滤。
  3. WeatherReport包含JOIN以在记录中提供WeatherStationName
CREATE VIEW vw_WeatherStation
AS
SELECT        
    WeatherStationID AS ID, 
    Name, 
    Latitude, 
    Longitude, 
    Elevation, 
    Name AS DisplayName
FROM WeatherStation
CREATE VIEW vw_WeatherReport
AS
SELECT        
    R.WeatherReportID as ID, 
    R.WeatherStationID, 
    R.Date, 
    R.TempMax, 
    R.TempMin, 
    R.FrostDays, 
    R.Rainfall, 
    R.SunHours, 
    S.Name AS WeatherStationName, 
    'Report For ' + CONVERT(VARCHAR(50), Date, 106) AS DisplayName
    MONTH(R.Date) AS Month, 
    YEAR(R.Date) AS Year
FROM  WeatherReport AS R 
LEFT INNER JOIN dbo.WeatherStation AS S ON R.WeatherStationID = S.WeatherStationID

为每种记录类型添加创建/更新/删除存储过程:

CREATE PROCEDURE sp_Create_WeatherStation
	@ID int output
    ,@Name decimal(4,1)
    ,@Latitude decimal(8,4)
    ,@Longitude decimal(8,4)
    ,@Elevation decimal(8,2)
AS
BEGIN
INSERT INTO dbo.WeatherStation
           ([Name]
           ,[Latitude]
           ,[Longitude]
           ,[Elevation])
     VALUES (@Name
           ,@Latitude
           ,@Longitude
           ,@Elevation)
SELECT @ID  = SCOPE_IDENTITY();
END
CREATE PROCEDURE sp_Update_WeatherStation
	@ID int
    ,@Name decimal(4,1)
    ,@Latitude decimal(8,4)
    ,@Longitude decimal(8,4)
    ,@Elevation decimal(8,2)
AS
BEGIN
UPDATE dbo.WeatherStation
	SET 
           [Name] = @Name
           ,[Latitude] = @Latitude
           ,[Longitude] = @Longitude
           ,[Elevation] = @Elevation
WHERE @ID  = WeatherStationID
END

   

CREATE PROCEDURE sp_Delete_WeatherStation
	@ID int
AS
BEGIN
DELETE FROM WeatherStation
WHERE @ID  = WeatherStationID
END
CREATE PROCEDURE sp_Create_WeatherReport
	@ID int output
    ,@WeatherStationID int
    ,@Date smalldatetime
    ,@TempMax decimal(8,4)
    ,@TempMin decimal(8,4)
    ,@FrostDays int
    ,@Rainfall decimal(8,4)
    ,@SunHours decimal(8,2)
AS
BEGIN
INSERT INTO WeatherReport
           ([WeatherStationID]
           ,[Date]
           ,[TempMax]
           ,[TempMin]
           ,[FrostDays]
           ,[Rainfall]
           ,[SunHours])
     VALUES
           (@WeatherStationID
           ,@Date
           ,@TempMax
           ,@TempMin
           ,@FrostDays
           ,@Rainfall
           ,@SunHours)
SELECT @ID  = SCOPE_IDENTITY();
END
CREATE PROCEDURE sp_Update_WeatherReport
	@ID int output
    ,@WeatherStationID int
    ,@Date smalldatetime
    ,@TempMax decimal(8,4)
    ,@TempMin decimal(8,4)
    ,@FrostDays int
    ,@Rainfall decimal(8,4)
    ,@SunHours decimal(8,2)
AS
BEGIN
UPDATE WeatherReport
   SET [WeatherStationID] = @WeatherStationID
      ,[Date] = @Date
      ,[TempMax] = @TempMax
      ,[TempMin] = @TempMin
      ,[FrostDays] = @FrostDays
      ,[Rainfall] = @Rainfall
      ,[SunHours] = @SunHours
WHERE @ID  = WeatherReportID
END
CREATE PROCEDURE sp_Delete_WeatherReport
	@ID int
AS
BEGIN
DELETE FROM WeatherReport
WHERE @ID  = WeatherReportID
END

包括两个气象站数据集在内的所有SQL都可以作为一组文件在GitHub RepositorySQL文件夹中使用。

CEC天气库

我们要:

  1. 为每种记录类型添加模型类。
  2. 添加一些特定于该项目的实用程序类。在这种情况下,我们
  • 添加一些扩展到decimal以正确显示我们的字段(LatitudeLongitude)。
  • 为每种记录类型的编辑器添加自定义验证器。
  • 更新WeatherForecastDBContext来处理新的记录类型。
  • 构建特定的ControllerData Services来处理每种记录类型。
  • 为每种记录类型构建特定的列表/编辑/查看表单。
  • 更新NavMenu组件。

为记录添加模型类

  1. 我们实现了IDbRecord
  2. 我们将SPParameter自定义属性添加到映射到存储过程的所有属性。
  3. 我们用[Not Mapped]来装饰未映射到数据库视图的属性。
// CEC.Weather/Model/DbWeatherStation.cs
public class DbWeatherStation :
    IDbRecord<DbWeatherStation>
{
    [NotMapped]
    public int WeatherStationID { get => this.ID; }
    
    [SPParameter(IsID = true, DataType = SqlDbType.Int)]
    public int ID { get; set; } = -1;

    [SPParameter(DataType = SqlDbType.VarChar)]
    public string Name { get; set; } = "No Name";

    [SPParameter(DataType = SqlDbType.Decimal)]
    [Column(TypeName ="decimal(8,4)")]
    public decimal Latitude { get; set; } = 1000;

    [SPParameter(DataType = SqlDbType.Decimal)]
    [Column(TypeName ="decimal(8,4)")]
    public decimal Longitude { get; set; } = 1000;

    [SPParameter(DataType = SqlDbType.Decimal)]
    [Column(TypeName ="decimal(8,2)")]
    public decimal Elevation { get; set; } = 1000;

    public string DisplayName { get; set; }

    [NotMapped]
    public string LatLong => $"{this.Latitude.AsLatitude()} {this.Longitude.AsLongitude()}";

    public void SetNew() => this.ID = 0;

    public DbWeatherStation ShadowCopy()
    {
        return new DbWeatherStation() {
            Name = this.Name,
            ID = this.ID,
            Latitude = this.Latitude,
            Longitude = this.Longitude,
            Elevation = this.Elevation,
            DisplayName = this.DisplayName
        };
    }
}
// CEC.Weather/Model/DbWeatherReport.cs
public class DbWeatherReport :IDbRecord<DbWeatherReport>
{
    [NotMapped]
    public int WeatherReportID { get => this.ID; }

    [SPParameter(IsID = true, DataType = SqlDbType.Int)]
    public int ID { get; set; } = -1;

    [SPParameter(DataType = SqlDbType.Int)]
    public int WeatherStationID { get; set; } = -1;

    [SPParameter(DataType = SqlDbType.SmallDateTime)]
    public DateTime Date { get; set; } = DateTime.Now.Date;

    [SPParameter(DataType = SqlDbType.Decimal)]
    [Column(TypeName ="decimal(8,4)")]
    public decimal TempMax { get; set; } = 1000;

    [SPParameter(DataType = SqlDbType.Decimal)]
    [Column(TypeName ="decimal(8,4)")]
    public decimal TempMin { get; set; } = 1000;

    [SPParameter(DataType = SqlDbType.Int)]
    public int FrostDays { get; set; } = -1;

    [SPParameter(DataType = SqlDbType.Decimal)]
    [Column(TypeName ="decimal(8,4)")]
    public decimal Rainfall { get; set; } = -1;

    [SPParameter(DataType = SqlDbType.Decimal)]
    [Column(TypeName ="decimal(8,2)")]
    public decimal SunHours { get; set; } = -1;

    public string DisplayName { get; set; }

    public string WeatherStationName { get; set; }

    public int Month { get; set; }

    public int Year { get; set; }

    [NotMapped]
    public string MonthName => 
           CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(this.Month);

    [NotMapped]
    public string MonthYearName => $"{this.MonthName}-{this.Year}";

    public void SetNew() => this.ID = 0;

    public DbWeatherReport ShadowCopy()
    {
        return new DbWeatherReport() {
            ID = this.ID,
            Date = this.Date,
            TempMax = this.TempMax,
            TempMin = this.TempMin,
            FrostDays = this.FrostDays,
            Rainfall = this.Rainfall,
            SunHours = this.SunHours,
            DisplayName = this.DisplayName,
            WeatherStationID = this.WeatherStationID,
            WeatherStationName = this.WeatherStationName
        };
    }
}

添加一些实用程序类

我坚信让生活更轻松。扩展方法对此非常有用。经度和纬度以小数形式处理,但我们需要在用户界面中以略微不同的方式显示它们。我们使用十进制扩展方法来做到这一点。

// CEC.Weather/Extensions/DecimalExtensions.cs
public static class DecimalExtensions
{
    public static string AsLatitude(this decimal value)  => 
                         value > 0 ? $"{value}N" : $"{Math.Abs(value)}S";

    public static string AsLongitude(this decimal value) => value > 0 ? 
                         $"{value}E" : $"{Math.Abs(value)}W";
}

该应用程序对编辑器使用Blazored Fluent验证。它比内置的验证更加灵活。

// CEC.Weather/Data/Validators/WeatherStationValidator.cs
using FluentValidation;

namespace CEC.Weather.Data.Validators
{
    public class WeatherStationValidator : AbstractValidator<DbWeatherStation>
    {
        public WeatherStationValidator()
        {
            RuleFor(p => p.Longitude).LessThan(-180).WithMessage
                                      ("Longitude must be -180 or greater");
            RuleFor(p => p.Longitude).GreaterThan(180).WithMessage
                                      ("Longitude must be 180 or less");
            RuleFor(p => p.Latitude).LessThan(-90).WithMessage
                                     ("Latitude must be -90 or greater");
            RuleFor(p => p.Latitude).GreaterThan(90).WithMessage
                                     ("Latitude must be 90 or less");
            RuleFor(p => p.Name).MinimumLength(1).WithMessage("Your need a Station Name!");
        }
    }
}
// CEC.Weather/Data/Validators/WeatherReportValidator.cs
using FluentValidation;

namespace CEC.Weather.Data.Validators
{
    public class WeatherReportValidator : AbstractValidator<DbWeatherReport>
    {
        public WeatherReportValidator()
        {
            RuleFor(p => p.Date).NotEmpty().WithMessage("You must select a date");
            RuleFor(p => p.TempMax).LessThan(60).WithMessage
                                    ("The temperature must be less than 60C");
            RuleFor(p => p.TempMax).GreaterThan(-40).WithMessage
                                    ("The temperature must be greater than -40C");
            RuleFor(p => p.TempMin).LessThan(60).WithMessage
                                    ("The temperature must be less than 60C");
            RuleFor(p => p.TempMin).GreaterThan(-40).WithMessage
                                    ("The temperature must be greater than -40C");
            RuleFor(p => p.FrostDays).LessThan(32).WithMessage
                                    ("There's a maximum of 31 days in any month");
            RuleFor(p => p.FrostDays).GreaterThan(0).WithMessage("valid entries are 0-31");
            RuleFor(p => p.Rainfall).GreaterThan(0).WithMessage("valid entries are 0-31");
            RuleFor(p => p.SunHours).LessThan(24).WithMessage("Valid entries 0-24");
            RuleFor(p => p.SunHours).GreaterThan(0).WithMessage("Valid entries 0-24");
        }
    }
}

更新WeatherForecastDbContext

在类中添加两个新DbSet属性,并在OnModelCreatingmodelBuilder调用。

// CEC.Weather/Data/WeatherForecastDbContext.cs
......

public DbSet<DbWeatherStation> WeatherStation { get; set; }

public DbSet<DbWeatherReport> WeatherReport { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
......
    modelBuilder
        .Entity<DbWeatherStation>(eb =>
        {
            eb.HasNoKey();
            eb.ToView("vw_WeatherStation");
        });
    modelBuilder
        .Entity<DbWeatherReport>(eb =>
        {
            eb.HasNoKey();
            eb.ToView("vw_WeatherReport");
        });
}

添加数据和控制器服务

我们仅在此处显示气象站服务代码——天气报告服务相同。

添加IWeatherStationDataServiceIWeatherReportDataService接口。

// CEC.Weather/Services/Interfaces/IWeatherStationDataService.cs
using CEC.Blazor.Services;
using CEC.Weather.Data;

namespace CEC.Weather.Services
{
    public interface IWeatherStationDataService : 
        IDataService<DbWeatherStation, WeatherForecastDbContext>
    {}
}

添加服务器数据服务。

// CEC.Weather/Services/DataServices/WeatherStationServerDataService.cs
using CEC.Blazor.Data;
using CEC.Weather.Data;
using CEC.Blazor.Services;
using Microsoft.Extensions.Configuration;

namespace CEC.Weather.Services
{
    public class WeatherStationServerDataService :
        BaseServerDataService<DbWeatherStation, WeatherForecastDbContext>,
        IWeatherStationDataService
    {
        public WeatherStationServerDataService
               (IConfiguration configuration, IDbContextFactory<WeatherForecastDbContext> 
                dbcontext) : base(configuration, dbcontext)
        {
            this.RecordConfiguration = new RecordConfigurationData() 
                 { RecordName = "WeatherStation", RecordDescription = "Weather Station", 
                   RecordListName = "WeatherStation", 
                   RecordListDecription = "Weather Stations" };
        }
    }
}

添加WASM数据服务:

// CEC.Weather/Services/DataServices/WeatherStationWASMDataService.cs
using CEC.Weather.Data;
using CEC.Blazor.Services;
using Microsoft.Extensions.Configuration;
using System.Net.Http;
using CEC.Blazor.Data;

namespace CEC.Weather.Services
{
    public class WeatherStationWASMDataService :
        BaseWASMDataService<DbWeatherStation, WeatherForecastDbContext>,
        IWeatherStationDataService
    {
        public WeatherStationWASMDataService(IConfiguration configuration, 
               HttpClient httpClient) : base(configuration, httpClient)
        {
            this.RecordConfiguration = new RecordConfigurationData() 
                 { RecordName = "WeatherStation", RecordDescription = "Weather Station", 
                   RecordListName = "WeatherStation", 
                   RecordListDecription = "Weather Stations" };
        }
    }
}

添加控制器服务:

// CEC.Weather/Services/ControllerServices/WeatherStationControllerService.cs
using CEC.Weather.Data;
using CEC.Blazor.Services;
using CEC.Blazor.Utilities;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Configuration;
using System.Collections.Generic;

namespace CEC.Weather.Services
{
    public class WeatherStationControllerService : 
           BaseControllerService<DbWeatherStation, WeatherForecastDbContext>, 
           IControllerService<DbWeatherStation, WeatherForecastDbContext>
    {
        /// <summary>
        /// List of Outlooks for Select Controls
        /// </summary>
        public SortedDictionary<int, string> 
               OutlookOptionList => Utils.GetEnumList<WeatherOutlook>();

        public WeatherStationControllerService
               (NavigationManager navmanager, IConfiguration appconfiguration, 
               IWeatherStationDataService DataService) : base(appconfiguration, navmanager)
        {
            this.Service = DataService;
            this.DefaultSortColumn = "ID";
        }
    }
}

表单

这些表单在很大程度上依赖于各自基类中的样板代码。代码页相对简单,而Razor标记页包含特定于记录的UI信息。

WeatherStation查看器表单

页面后面的代码很简单——一切都由RecordComponentBase中的样板代码处理。

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

using CEC.Blazor.Components.BaseForms;
using CEC.Weather.Data;
using CEC.Weather.Services;
using Microsoft.AspNetCore.Components;
using System.Threading.Tasks;

namespace CEC.Weather.Components
{
    public partial class WeatherStationViewerForm : 
           RecordComponentBase<DbWeatherStation, WeatherForecastDbContext>
    {
        [Inject]
        private WeatherStationControllerService ControllerService { get; set; }

        protected async override Task OnInitializedAsync()
        {
            this.Service = this.ControllerService;
            // Set the delay on the record load as this is a demo project
            this.DemoLoadDelay = 0;
            await base.OnInitializedAsync();
        }
    }
}

Razor页面构建了用于显示记录字段的UI控件。请注意,我们必须在标记中使用@using语句,因为这是一个没有_Imports.Razor的库文件。

// CEC.Weather/Components/Forms/WeatherStationViewerForm.razor

@using CEC.Blazor.Components
@using CEC.Blazor.Components.BaseForms
@using CEC.Blazor.Components.UIControls
@using CEC.Weather.Data
@using CEC.FormControls.Components.FormControls

@namespace CEC.Weather.Components
@inherits RecordComponentBase<DbWeatherReport, WeatherForecastDbContext>

<UICard>
    <Header>
        @this.PageTitle
    </Header>
    <Body>
        <UIErrorHandler IsError="this.IsError" IsLoading="this.IsDataLoading" 
                        ErrorMessage="@this.RecordErrorMessage">
            <UIContainer>
                <UIRow>
                    <UILabelColumn Columns="2">
                        Month/Year
                    </UILabelColumn>
                    <UIColumn Columns="2">
                        <FormControlPlainText Value="@this.Service.Record.MonthYearName">
                        </FormControlPlainText>
                    </UIColumn>
                    <UILabelColumn Columns="2">
                        Station
                    </UILabelColumn>
                    <UIColumn Columns="4">
                        <FormControlPlainText 
                         Value="@this.Service.Record.WeatherStationName">
                        </FormControlPlainText>
                    </UIColumn>
                    <UILabelColumn Columns="1">
                        ID
                    </UILabelColumn>
                    <UIColumn Columns="1">
                        <FormControlPlainText Value="@this.Service.Record.ID.ToString()">
                        </FormControlPlainText>
                    </UIColumn>
                </UIRow>
                <UIRow>
                    <UILabelColumn Columns="2">
                        Max Temperature ° C:
                    </UILabelColumn>
                    <UIColumn Columns="4">
                        <FormControlPlainText Value="@this.Service.Record.TempMax.ToString()">
                        </FormControlPlainText>
                    </UIColumn>
                    <UILabelColumn Columns="2">
                        Min Temperature ° C:
                    </UILabelColumn>
                    <UIColumn Columns="4">
                        <FormControlPlainText Value="@this.Service.Record.TempMin.ToString()">
                        </FormControlPlainText>
                    </UIColumn>
                </UIRow>
                <UIRow>
                    <UILabelColumn Columns="2">
                        Frost Days
                    </UILabelColumn>
                    <UIColumn Columns="2">
                        <FormControlPlainText Value="@this.Service.Record.FrostDays.ToString()">
                        </FormControlPlainText>
                    </UIColumn>
                    <UILabelColumn Columns="2">
                        Rainfall (mm)
                    </UILabelColumn>
                    <UIColumn Columns="2">
                        <FormControlPlainText Value="@this.Service.Record.Rainfall.ToString()">
                        </FormControlPlainText>
                    </UIColumn>
                    <UILabelColumn Columns="2">
                        Sunshine (hrs)
                    </UILabelColumn>
                    <UIColumn Columns="2">
                        <FormControlPlainText Value="@this.Service.Record.SunHours.ToString()">
                        </FormControlPlainText>
                    </UIColumn>
                </UIRow>
            </UIContainer>
        </UIErrorHandler>
        <UIContainer>
            <UIRow>
                <UIButtonColumn Columns="12">
                    <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>
</UICard>

WeatherStation编辑器表单

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

using CEC.Blazor.Components.BaseForms;
using CEC.Weather.Data;
using CEC.Weather.Services;
using Microsoft.AspNetCore.Components;
using System.Threading.Tasks;

namespace CEC.Weather.Components
{
    public partial class WeatherStationEditorForm : 
           EditRecordComponentBase<DbWeatherStation, WeatherForecastDbContext>
    {
        [Inject]
        public WeatherStationControllerService ControllerService { get; set; }

        protected async override Task OnInitializedAsync()
        {
            // Assign the correct controller service
            this.Service = this.ControllerService;
            // Set the delay on the record load as this is a demo project
            this.DemoLoadDelay = 0;
            await base.OnInitializedAsync();
        }
    }
}
// CEC.Weather/Components/Forms/WeatherStationEditorForm.razor
@using CEC.Blazor.Components
@using CEC.Blazor.Components.BaseForms
@using CEC.Blazor.Components.UIControls
@using CEC.Weather.Data
@using CEC.FormControls.Components.FormControls
@using Microsoft.AspNetCore.Components.Forms
@using Blazored.FluentValidation

@namespace CEC.Weather.Components
@inherits EditRecordComponentBase<DbWeatherStation, WeatherForecastDbContext>

<UICard IsCollapsible="false">
    <Header>
        @this.PageTitle
    </Header>
    <Body>
        <CascadingValue Value="@this.RecordFieldChanged" Name="OnRecordChange" 
                        TValue="Action<bool>">
            <UIErrorHandler IsError="@this.IsError" IsLoading="this.IsDataLoading" 
                            ErrorMessage="@this.RecordErrorMessage">
                <UIContainer>
                    <EditForm EditContext="this.EditContext">
                        <FluentValidationValidator DisableAssemblyScanning="@true" />
                        <UIFormRow>
                            <UILabelColumn Columns="4">
                                Record ID:
                            </UILabelColumn>
                            <UIColumn Columns="4">
                                <FormControlPlainText 
                                 Value="@this.Service.Record.ID.ToString()">
                                </FormControlPlainText>
                            </UIColumn>
                        </UIFormRow>
                        <UIFormRow>
                            <UILabelColumn Columns="4">
                                Name:
                            </UILabelColumn>
                            <UIColumn Columns="4">
                                <FormControlText class="form-control" 
                                 @bind-Value="this.Service.Record.Name" 
                                 RecordValue="this.Service.ShadowRecord.Name">
                                </FormControlText>
                            </UIColumn>
                            <UIColumn Columns="4">
                                <ValidationMessage For=@(() => this.Service.Record.Name) />
                            </UIColumn>
                        </UIFormRow>
                        <UIFormRow>
                            <UILabelColumn Columns="4">
                                Latitude
                            </UILabelColumn>
                            <UIColumn Columns="2">
                                <FormControlNumber class="form-control" 
                                 @bind-Value="this.Service.Record.Latitude" 
                                 RecordValue="this.Service.ShadowRecord.Latitude">
                                </FormControlNumber>
                            </UIColumn>
                            <UIColumn Columns="6">
                                <ValidationMessage 
                                 For=@(() => this.Service.Record.Latitude) />
                            </UIColumn>
                        </UIFormRow>
                        <UIFormRow>
                            <UILabelColumn Columns="4">
                                Longitude
                            </UILabelColumn>
                            <UIColumn Columns="2">
                                <FormControlNumber class="form-control" 
                                 @bind-Value="this.Service.Record.Longitude" 
                                 RecordValue="this.Service.ShadowRecord.Longitude">
                                </FormControlNumber>
                            </UIColumn>
                            <UIColumn Columns="6">
                                <ValidationMessage 
                                 For=@(() => this.Service.Record.Longitude) />
                            </UIColumn>
                        </UIFormRow>
                        <UIFormRow>
                            <UILabelColumn Columns="4">
                                Elevation
                            </UILabelColumn>
                            <UIColumn Columns="2">
                                <FormControlNumber class="form-control" 
                                 @bind-Value="this.Service.Record.Elevation" 
                                 RecordValue="this.Service.ShadowRecord.Elevation">
                                </FormControlNumber>
                            </UIColumn>
                            <UIColumn Columns="6">
                                <ValidationMessage 
                                 For=@(() => this.Service.Record.Elevation) />
                            </UIColumn>
                        </UIFormRow>
                    </EditForm>
                </UIContainer>
            </UIErrorHandler>
            <UIContainer>
                <UIRow>
                    <UIColumn Columns="7">
                        <UIAlert Alert="this.AlertMessage" 
                         SizeCode="Bootstrap.SizeCode.sm"></UIAlert>
                    </UIColumn>
                    <UIButtonColumn Columns="5">
                        <UIButton Show="this.NavigationCancelled && this.IsLoaded" 
                         ClickEvent="this.Cancel" ColourCode="Bootstrap.ColourCode.cancel">
                         Cancel</UIButton>
                        <UIButton Show="this.NavigationCancelled && this.IsLoaded" 
                         ClickEvent="this.SaveAndExit" ColourCode="Bootstrap.ColourCode.save">
                         Save & Exit</UIButton>
                        <UIButton Show="(!this.IsClean) && this.IsLoaded" 
                         ClickEvent="this.Save" ColourCode="Bootstrap.ColourCode.save">
                         Save</UIButton>
                        <UIButton Show="this.ShowExitConfirmation && this.IsLoaded" 
                         ClickEvent="this.ConfirmExit" 
                         ColourCode="Bootstrap.ColourCode.danger_exit">
                         Exit Without Saving</UIButton>
                        <UIButton Show="(!this.NavigationCancelled) && 
                         !this.ShowExitConfirmation" 
                         ClickEvent="(e => this.NavigateTo(PageExitType.ExitToList))" 
                         ColourCode="Bootstrap.ColourCode.nav">Exit To List</UIButton>
                        <UIButton Show="(!this.NavigationCancelled) && 
                         !this.ShowExitConfirmation" ClickEvent="this.Exit" 
                         ColourCode="Bootstrap.ColourCode.nav">Exit</UIButton>
                    </UIButtonColumn>
                </UIRow>
            </UIContainer>
        </CascadingValue>
    </Body>
</UICard>

WeatherStation列表表单

// CEC.Weather/Components/Forms/WeatherStation/WeatherStationListForm.razor.cs
@using CEC.Blazor.Components
@using CEC.Blazor.Components.BaseForms
@using CEC.Blazor.Components.UIControls
@using CEC.Weather.Data
@using CEC.Weather.Extensions
@using CEC.Blazor.Extensions

@namespace CEC.Weather.Components

@inherits ListComponentBase<DbWeatherStation, WeatherForecastDbContext>

<UIWrapper UIOptions="@this.UIOptions" 
 RecordConfiguration="@this.Service.RecordConfiguration" OnView="@OnView" OnEdit="@OnEdit">
    <UICardGrid TRecord="DbWeatherStation" IsCollapsible="true" 
     Paging="this.Paging" IsLoading="this.Loading">
        <Title>
            @this.ListTitle
        </Title>
        <TableHeader>
            <UIGridTableHeaderColumn TRecord="DbWeatherStation" 
             Column="1" FieldName="ID">ID</UIGridTableHeaderColumn>
            <UIGridTableHeaderColumn TRecord="DbWeatherStation" 
             Column="2" FieldName="Name">Name</UIGridTableHeaderColumn>
            <UIGridTableHeaderColumn TRecord="DbWeatherStation" 
             Column="3" FieldName="Latitude">Latitiude</UIGridTableHeaderColumn>
            <UIGridTableHeaderColumn TRecord="DbWeatherStation" 
             Column="4" FieldName="Longitude">Longitude</UIGridTableHeaderColumn>
            <UIGridTableHeaderColumn TRecord="DbWeatherStation" 
             Column="5" FieldName="Elevation">Elevation</UIGridTableHeaderColumn>
            <UIGridTableHeaderColumn TRecord="DbWeatherStation" 
             Column="6"></UIGridTableHeaderColumn>
        </TableHeader>
        <RowTemplate>
            <CascadingValue Name="RecordID" Value="@context.ID">
                <UIGridTableColumn TRecord="DbWeatherStation" 
                 Column="1">@context.ID</UIGridTableColumn>
                <UIGridTableColumn TRecord="DbWeatherStation" 
                 Column="2">@context.Name</UIGridTableColumn>
                <UIGridTableColumn TRecord="DbWeatherStation" 
                 Column="3">@context.Latitude.AsLatitude()</UIGridTableColumn>
                <UIGridTableColumn TRecord="DbWeatherStation" 
                 Column="4">@context.Longitude.AsLongitude()</UIGridTableColumn>
                <UIGridTableColumn TRecord="DbWeatherStation" 
                 Column="5">@context.Elevation.DecimalPlaces(1)</UIGridTableColumn>
                <UIGridTableEditColumn TRecord="DbWeatherStation"></UIGridTableEditColumn>
            </CascadingValue>
        </RowTemplate>
        <Navigation>
            <UIListButtonRow>
                <Paging>
                    <PagingControl TRecord="DbWeatherStation" Paging="this.Paging">
                    </PagingControl>
                </Paging>
            </UIListButtonRow>
        </Navigation>
    </UICardGrid>
</UIWrapper>
<BootstrapModal @ref="this._BootstrapModal"></BootstrapModal>
// CEC.Weather/Components/Forms/WeatherStation/WeatherStationListForm.razor.cs
using Microsoft.AspNetCore.Components;
using CEC.Blazor.Components.BaseForms;
using CEC.Weather.Data;
using CEC.Weather.Services;
using System.Threading.Tasks;

namespace CEC.Weather.Components
{
    public partial class WeatherStationListForm : 
           ListComponentBase<DbWeatherStation, WeatherForecastDbContext>
    {
        /// The Injected Controller service for this record
        [Inject]
        protected WeatherStationControllerService ControllerService { get; set; }


        protected async override Task OnInitializedAsync()
        {
            this.UIOptions.MaxColumn = 2;
            this.Service = this.ControllerService;
            await base.OnInitializedAsync();
        }

        /// Method called when the user clicks on a row in the viewer.
        protected void OnView(int id) => this.OnViewAsync<WeatherStationViewerForm>(id);

        /// Method called when the user clicks on a row Edit button.
        protected void OnEdit(int id) => this.OnEditAsync<WeatherStationEditorForm>(id);
    }
}

天气报告表单

您可以从GitHub Repository获得这些。它们与气象站表单相同,除了在编辑器中,我们有一个气象站选择和查找列表。编辑器表单中的部分如下所示:

// CEC.Weather/Components/Forms/WeatherReport/WeatherReportEditorForm.razor
<UIFormRow>
    <UILabelColumn Columns="4">
        Station:
    </UILabelColumn>
    <UIColumn Columns="4">
        <InputControlSelect OptionList="this.StationLookupList" 
         @bind-Value="this.Service.Record.WeatherStationID" 
         RecordValue="@this.Service.ShadowRecord.WeatherStationID"></InputControlSelect>
    </UIColumn>
</UIFormRow>

通过调用控制器服务中的泛型GetLookUpListAsync\<IRecord\>()方法来在OnParametersSetAsync中加载StationLookupList属性。我们指定实际的记录类型——在这个例子DbWeatherStation中——该方法将回调相关的数据服务,该数据服务将发挥其魔力(在CEC.Blazor/Extensions中的DBContextExtensions中的GetRecordLookupListAsync),并返回一个包含记录IDDisplayName属性的SortedDictionary列表。

// CEC.Weather/Components/Forms/WeatherReport/WeatherReportEditorForm.razor.cs

    public partial class WeatherReportEditorForm : 
           EditRecordComponentBase<DbWeatherReport, WeatherForecastDbContext>
    {
        .......
        // Property to hold the Station Lookup List
        public SortedDictionary<int, string> StationLookupList { get; set; }

        .......
        protected async override Task OnParametersSetAsync()
        {
            await base.OnParametersSetAsync();
            // Method to get the Station Lookup List.
            // Called here so whenever we do a UI refresh we get the list, 
            // we never know when it might be updated
            StationLookupList = await this.Service.GetLookUpListAsync<DbWeatherStation>();
        }
    }

过滤器加载是以下中ListComponentBaseOnParametersSetAsync过程的一部分:

// CEC.Blazor/Components/BaseForms/ListComponentBase.cs

protected async override Task OnParametersSetAsync()
{
    await base.OnParametersSetAsync();
    // Load the page - as we've reset everything this will be the first page 
    // with the default filter
    if (this.IsService)
    {
        // Load the filters for the recordset
        this.LoadFilter();
        // Load the paged recordset
        await this.Service.LoadPagingAsync();
    }
    this.Loading = false;
}

/// Method called to load the filter
protected virtual void LoadFilter()
{
    if (IsService) this.Service.FilterList.OnlyLoadIfFilters = this.OnlyLoadIfFilter;
}

WeatherReportListForm覆盖LoadFilter以设置记录特定的过滤器。

// CEC.Weather/Components/Forms/WeatherReport/WeatherReportListForm.razor.cs
.....
[Parameter]
public int WeatherStationID { get; set; }
.......
/// inherited - loads the filter
protected override void LoadFilter()
{
    // Before the call to base so the filter is set before the get the list
    if (this.IsService &&  this.WeatherStationID > 0)
    {
        this.Service.FilterList.Filters.Clear();
        this.Service.FilterList.Filters.Add("WeatherStationID", this.WeatherStationID);
    }
    base.LoadFilter();
}
......

导航菜单

NavMenu中添加菜单链接。

// CEC.Weather/Components/Controls/NavMenu.cs
    .....
    <li class="nav-item px-3">
        <NavLink class="nav-link" href="weatherforecastmodal">
            <span class="oi oi-cloud-upload" aria-hidden="true"></span> Modal Weather
        </NavLink>
    </li>
    <li class="nav-item px-3">
        <NavLink class="nav-link" href="weatherstation">
            <span class="oi oi-cloudy" aria-hidden="true"></span> Weather Stations
        </NavLink>
    </li>
    <li class="nav-item px-3">
        <NavLink class="nav-link" href="weatherreport">
            <span class="oi oi-cloudy" aria-hidden="true"></span> Weather Reports
        </NavLink>
    </li>
    <li class="nav-item px-3">
        <NavLink class="nav-link" href="https://github.com/ShaunCurtis/CEC.Blazor">
            <span class="oi oi-fork" aria-hidden="true"></span> Github Repo
        </NavLink>
    </li>
    ......

过滤器控件

添加一个名为MonthYearIDListFilter的新控件。在WestherReport列表视图中使用它来过滤记录。

// CEC.Weather/Components/Controls/MonthYearIDListFilter.razor.cs
using CEC.Blazor.Data;
using CEC.Weather.Data;
using CEC.Weather.Services;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using System.Collections.Generic;
using System.Globalization;
using System.Threading.Tasks;

namespace CEC.Weather.Components
{
    public partial class MonthYearIDListFilter : ComponentBase
    {
        // Inject the Controller Service
        [Inject]
        private WeatherReportControllerService Service { get; set; }

        // Boolean to control the ID Control Display
        [Parameter]
        public bool ShowID { get; set; } = true;

        // Month Lookup List
        private SortedDictionary<int, string> MonthLookupList { get; set; }

        // Year Lookup List
        private SortedDictionary<int, string> YearLookupList { get; set; }

        // Weather Station Lookup List
        private SortedDictionary<int, string> IdLookupList { get; set; }

        // Dummy Edit Context for selects
        private EditContext EditContext => new EditContext(this.Service.Record);

        // privates to hold current select values
        private int OldMonth = 0;
        private int OldYear = 0;
        private long OldID = 0;

        // Month value - adds or removes the value from the filter list 
        // and kicks off Filter changed if changed
        private int Month
        {
            get => this.Service.FilterList.TryGetFilter("Month", out object value) ? 
                   (int)value : 0;
            set
            {
                if (value > 0) this.Service.FilterList.SetFilter("Month", value);
                else this.Service.FilterList.ClearFilter("Month");
                if (this.Month != this.OldMonth)
                {
                    this.OldMonth = this.Month;
                    this.Service.TriggerFilterChangedEvent(this);
                }
            }
        }

        // Year value - adds or removes the value from the filter list and 
        // kicks off Filter changed if changed
        private int Year
        {
            get => this.Service.FilterList.TryGetFilter("Year", out object value) ? 
                   (int)value : 0;
            set
            {
                if (value > 0) this.Service.FilterList.SetFilter("Year", value);
                else this.Service.FilterList.ClearFilter("Year");
                if (this.Year != this.OldYear)
                {
                    this.OldYear = this.Year;
                    this.Service.TriggerFilterChangedEvent(this);
                }
            }
        }

        // Weather Station value - adds or removes the value from the filter list 
        // and kicks off Filter changed if changed
        private int ID
        {
            get => this.Service.FilterList.TryGetFilter
                   ("WeatherStationID", out object value) ? (int)value : 0;
            set
            {
                if (value > 0) this.Service.FilterList.SetFilter("WeatherStationID", value);
                else this.Service.FilterList.ClearFilter("WeatherStationID");
                if (this.ID != this.OldID)
                {
                    this.OldID = this.ID;
                    this.Service.TriggerFilterChangedEvent(this);
                }
            }
        }

        protected override async Task OnInitializedAsync()
        {
            this.OldYear = this.Year;
            this.OldMonth = this.Month;
            await GetLookupsAsync();
        }

        // Method to get he LokkupLists
        protected async Task GetLookupsAsync()
        {
            this.IdLookupList = await this.Service.GetLookUpListAsync<DbWeatherStation>
                                ("-- ALL STATIONS --");
            // Get the months in the year
            this.MonthLookupList = new SortedDictionary<int, string> 
                                   { { 0, "-- ALL MONTHS --" } };
            for (int i = 1; i < 13; i++) this.MonthLookupList.Add
                (i, CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(i));
            // Gets a distinct list of Years in the Weather Reports
            {
                var list = await this.Service.GetDistinctListAsync(new DbDistinctRequest() 
                           { FieldName = "Year", QuerySetName = "WeatherReport", 
                           DistinctSetName = "DistinctList" });
                this.YearLookupList = new SortedDictionary<int, string> 
                           { { 0, "-- ALL YEARS --" } };
                list.ForEach(item => this.YearLookupList.Add(int.Parse(item), item));
            }
        }
    }
}
// CEC.Weather/Components/Controls/MonthYearIDListFilter.razor
@using CEC.Blazor.Components.FormControls
@using Microsoft.AspNetCore.Components.Forms

@namespace CEC.Weather.Components
@inherits ComponentBase

<EditForm EditContext="this.EditContext">

    <table class="table">
        <tr>
            @if (this.ShowID)
            {
                <!--Weather Station-->
                <td>
                    <label class="" for="ID">Weather Station:</label>
                    <div class="">
                        <InputControlSelect OptionList="this.IdLookupList" 
                         @bind-Value="this.ID"></InputControlSelect>
                    </div>
                </td>
            }
            <td>
                <!--Month-->
                <label class="">Month:</label>
                <div class="">
                    <InputControlSelect OptionList="this.MonthLookupList" 
                     @bind-Value="this.Month"></InputControlSelect>
                </div>
            </td>
            <td>
                <!--Year-->
                <label class="">Year:</label>
                <div class="">
                    <InputControlSelect OptionList="this.YearLookupList" 
                     @bind-Value="this.Year"></InputControlSelect>
                </div>
            </td>
        </tr>
    </table>
</EditForm>

过滤器显示一组下拉菜单。更改值时,将从过滤器列表中添加、更新或删除该值,并启动服务FilterUpdated事件。这会触发一系列事件,从而启动ListForm UI更新。

CEC.Blazor.Server

现在所有共享代码都已完成,我们需要移至实际项目。

要设置服务器,我们需要

  1. 配置正确的服务——特定于服务器。
  2. 为每种记录类型构建视图——这些视图与WASM客户端中使用的视图相同。

Startup.cs

我们需要用新的服务来更新启动,通过更新在ServiceCollectionExtensions.cs中的AddApplicationServices

注意xxxxxxServerDataService已添加为IxxxxxxDataService

// CEC.Blazor.Server/Extensions/ServiceCollectionExtensions.cs
public static IServiceCollection AddApplicationServices
(this IServiceCollection services, IConfiguration configuration)
{
    // Singleton service for the Server Side version of each Data Service 
    services.AddSingleton<IWeatherForecastDataService, WeatherForecastServerDataService>();
    services.AddSingleton<IWeatherStationDataService, WeatherStationServerDataService>();
    services.AddSingleton<IWeatherReportDataService, WeatherReportServerDataService>();
    // Scoped service for each Controller Service
    services.AddScoped<WeatherForecastControllerService>();
    services.AddScoped<WeatherStationControllerService>();
    services.AddScoped<WeatherReportControllerService>();
    // Transient service for the Fluent Validator for each record
    services.AddTransient<IValidator<DbWeatherForecast>, WeatherForecastValidator>();
    services.AddTransient<IValidator<DbWeatherStation>, WeatherStationValidator>();
    services.AddTransient<IValidator<DbWeatherReport>, WeatherReportValidator>();
    // Factory for building the DBContext 
    var dbContext = configuration.GetValue<string>("Configuration:DBContext");
    services.AddDbContextFactory<WeatherForecastDbContext>
    (options => options.UseSqlServer(dbContext), ServiceLifetime.Singleton);
    return services;
}

气象站路由/视图

这些几乎是微不足道的。所有代码和标记都在表单中。我们只是声明路由并将表单添加到视图中。

// CEC.Blazor.Server/Routes/WeatherStation/WeatherStationEditorView.razor
@page "/WeatherStation/New"
@page "/WeatherStation/Edit"

@inherits ApplicationComponentBase
@namespace CEC.Blazor.Server.Routes

<WeatherStationEditorForm></WeatherStationEditorForm>
// CEC.Blazor.Server/Routes/WeatherStation/WeatherStationListView.razor
@page "/WeatherStation"
@namespace CEC.Blazor.Server.Routes
@inherits ApplicationComponentBase

<WeatherStationListForm UIOptions="this.UIOptions" ></WeatherStationListForm>

@code {
    public UIOptions UIOptions => new UIOptions()
    {
        ListNavigationToViewer = true,
        ShowButtons = true,
        ShowAdd = true,
        ShowEdit = true
    };
}

WeatherStation的视图稍微复杂一些。我们添加的查看表单WeatherStationWeatherReports的列表表单,并通过ID传递WeatherReport列表表单WeatherStation

@page "/WeatherStation/View"
@namespace CEC.Blazor.Server.Routes
@inherits ApplicationComponentBase

<WeatherStationViewerForm></WeatherStationViewerForm>
<UIBootstrapBase Css="mt-2">
    <WeatherReportListForm WeatherStationID="this._ID" OnlyLoadIfFilter="true">
    </WeatherReportListForm>
</UIBootstrapBase>
// CEC.Blazor.Server/Routes/WeatherReport/WeatherReportEditorView.razor
@page "/WeatherReport/New"
@page "/WeatherReport/Edit"

@inherits ApplicationComponentBase

@namespace CEC.Blazor.Server.Routes

<WeatherReportEditorForm></WeatherReportEditorForm>

WeatherReportListView使用MonthYearIdListFilter控制天气报告列表。注意OnlyLoadIfFilter设置为true以防止未设置过滤器时显示全部recordset内容。

// CEC.Blazor.Server/Routes/WeatherReport/WeatherReportListView.razor
@page "/WeatherReport"

@namespace CEC.Blazor.Server.Routes
@inherits ApplicationComponentBase

<MonthYearIDListFilter></MonthYearIDListFilter>
<WeatherReportListForm OnlyLoadIfFilter="true" UIOptions="this.UIOptions">
</WeatherReportListForm>

@code {
    public UIOptions UIOptions => new UIOptions()
    {
        ListNavigationToViewer = true,
        ShowButtons = true,
        ShowAdd = true,
        ShowEdit = true
    };
}
// CEC.Blazor.Server/Routes/WeatherReport/WeatherReportViewerView.razor
@page "/WeatherReport/View"

@namespace CEC.Blazor.Server.Routes
@inherits ApplicationComponentBase

<WeatherReportViewerForm></WeatherReportViewerForm>

CEC.Blazor.WASM.Client

要设置客户端,我们需要

  1. 配置正确的服务——特定于客户端。
  2. 为每种记录类型构建视图——与服务器相同。

program.cs

我们需要使用新服务来更新程序。我们通过在ServiceCollectionExtensions.cs进行更新AddApplicationServices来实现。

xxxxxxWASMDataService添加为IxxxxxxDataService

// CEC.Blazor.WASM/Client/Extensions/ServiceCollectionExtensions.cs
public static IServiceCollection AddApplicationServices
(this IServiceCollection services, IConfiguration configuration)
{
    // Scoped service for the WASM Client version of Data Services 
    services.AddScoped<IWeatherForecastDataService, WeatherForecastWASMDataService>();
    services.AddScoped<IWeatherStationDataService, WeatherStationWASMDataService>();
    services.AddScoped<IWeatherReportDataService, WeatherReportWASMDataService>();
    // Scoped service for the Controller Services
    services.AddScoped<WeatherForecastControllerService>();
    services.AddScoped<WeatherStationControllerService>();
    services.AddScoped<WeatherReportControllerService>();
    // Transient service for the Fluent Validator for the records
    services.AddTransient<IValidator<DbWeatherForecast>, WeatherForecastValidator>();
    services.AddTransient<IValidator<DbWeatherStation>, WeatherStationValidator>();
    services.AddTransient<IValidator<DbWeatherReport>, WeatherReportValidator>();
    return services;
}

气象站路由/视图

这些与服务器完全相同。所以我在这里不再重复。

就是这样!客户端已配置。

CEC.Blazor.WASM.Server

WASM服务器是API提供程序。我们要:

  1. 配置正确的服务。
  2. 为每种记录类型构建控制器。

Startup.cs

我们需要使用新服务来更新启动。我们通过在ServiceCollectionExtensions.cs进行更新AddApplicationServices来实现。

xxxxxxServerDataService添加为IxxxxxxDataService

// CEC.Blazor.WASM.Server/Extensions/ServiceCollectionExtensions.cs
public static IServiceCollection AddApplicationServices
(this IServiceCollection services, IConfiguration configuration)
{
    // Singleton service for the Server Side version of each Data Service 
    services.AddSingleton<IWeatherForecastDataService, WeatherForecastServerDataService>();
    services.AddSingleton<IWeatherStationDataService, WeatherStationServerDataService>();
    services.AddSingleton<IWeatherReportDataService, WeatherReportServerDataService>();
    // Factory for building the DBContext 
    var dbContext = configuration.GetValue<string>("Configuration:DBContext");
    services.AddDbContextFactory<WeatherForecastDbContext>
    (options => options.UseSqlServer(dbContext), ServiceLifetime.Singleton);
    return services;
}

气象站控制器

控制器充当每种服务的数据控制器的网关。它们是不言自明的。我们使用HttpgGet来发出数据请求,使用HttpPost来发布信息到API。每种记录类型的控制器具有相同的模式——构建新记录是复制和替换练习。

// CEC.Blazor.WASM.Server/Controllers/WeatherStationController.cs
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using MVC = Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using CEC.Weather.Services;
using CEC.Weather.Data;
using CEC.Blazor.Data;
using CEC.Blazor.Components;

namespace CEC.Blazor.WASM.Server.Controllers
{
    [ApiController]
    public class WeatherStationController : ControllerBase
    {
        protected IWeatherStationDataService DataService { get; set; }

        private readonly ILogger<WeatherStationController> logger;

        public WeatherStationController(ILogger<WeatherStationController> logger, 
               IWeatherStationDataService dataService)
        {
            this.DataService = dataService;
            this.logger = logger;
        }

        [MVC.Route("weatherstation/list")]
        [HttpGet]
        public async Task<List<DbWeatherStation>> GetList() => 
               await DataService.GetRecordListAsync();

        [MVC.Route("weatherStation/filteredlist")]
        [HttpPost]
        public async Task<List<DbWeatherStation>> 
               GetFilteredRecordListAsync([FromBody] FilterList filterList) => 
               await DataService.GetFilteredRecordListAsync(filterList);

        [MVC.Route("weatherstation/base")]
        public async Task<List<DbBaseRecord>> GetBaseAsync() => 
               await DataService.GetBaseRecordListAsync<DbWeatherStation>();

        [MVC.Route("weatherstation/count")]
        [HttpGet]
        public async Task<int> Count() => await DataService.GetRecordListCountAsync();

        [MVC.Route("weatherstation/get")]
        [HttpGet]
        public async Task<DbWeatherStation> GetRec(int id) => 
               await DataService.GetRecordAsync(id);

        [MVC.Route("weatherstation/read")]
        [HttpPost]
        public async Task<DbWeatherStation> Read([FromBody]int id) => 
               await DataService.GetRecordAsync(id);

        [MVC.Route("weatherstation/update")]
        [HttpPost]
        public async Task<DbTaskResult> Update([FromBody]DbWeatherStation record) => 
               await DataService.UpdateRecordAsync(record);

        [MVC.Route("weatherstation/create")]
        [HttpPost]
        public async Task<DbTaskResult> Create([FromBody]DbWeatherStation record) => 
               await DataService.CreateRecordAsync(record);

        [MVC.Route("weatherstation/delete")]
        [HttpPost]
        public async Task<DbTaskResult> Delete([FromBody] 
               DbWeatherStation record) => await DataService.DeleteRecordAsync(record);
    }
}
// CEC.Blazor.WASM.Server/Controllers/WeatherReportController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using MVC = Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using CEC.Weather.Services;
using CEC.Weather.Data;
using CEC.Blazor.Data;
using CEC.Blazor.Components;

namespace CEC.Blazor.WASM.Server.Controllers
{
    [ApiController]
    public class WeatherReportController : ControllerBase
    {
        protected IWeatherReportDataService DataService { get; set; }

        private readonly ILogger<WeatherReportController> logger;

        public WeatherReportController(ILogger<WeatherReportController> logger, 
               IWeatherReportDataService dataService)
        {
            this.DataService = dataService;
            this.logger = logger;
        }

        [MVC.Route("weatherreport/list")]
        [HttpGet]
        public async Task<List<DbWeatherReport>> GetList() => 
               await DataService.GetRecordListAsync();

        [MVC.Route("weatherreport/filteredlist")]
        [HttpPost]
        public async Task<List<DbWeatherReport>> 
               GetFilteredRecordListAsync([FromBody] FilterList filterList) => 
               await DataService.GetFilteredRecordListAsync(filterList);

        [MVC.Route("weatherreport/distinctlist")]
        [HttpPost]
        public async Task<List<string>> 
               GetDistinctListAsync([FromBody] DbDistinctRequest req) => 
               await DataService.GetDistinctListAsync(req);

        [MVC.Route("weatherreport/base")]
        public async Task<List<DbBaseRecord>> GetBaseAsync() => 
               await DataService.GetBaseRecordListAsync<DbWeatherReport>();

        [MVC.Route("weatherreport/count")]
        [HttpGet]
        public async Task<int> Count() => await DataService.GetRecordListCountAsync();

        [MVC.Route("weatherreport/get")]
        [HttpGet]
        public async Task<DbWeatherReport> GetRec(int id) => 
                                           await DataService.GetRecordAsync(id);

        [MVC.Route("weatherreport/read")]
        [HttpPost]
        public async Task<DbWeatherReport> Read([FromBody]int id) => 
                                           await DataService.GetRecordAsync(id);

        [MVC.Route("weatherreport/update")]
        [HttpPost]
        public async Task<DbTaskResult> Update([FromBody]DbWeatherReport record) => 
                                        await DataService.UpdateRecordAsync(record);

        [MVC.Route("weatherreport/create")]
        [HttpPost]
        public async Task<DbTaskResult> Create([FromBody]DbWeatherReport record) => 
                                        await DataService.CreateRecordAsync(record);

        [MVC.Route("weatherreport/delete")]
        [HttpPost]
        public async Task<DbTaskResult> Delete([FromBody] 
               DbWeatherReport record) => await DataService.DeleteRecordAsync(record);
    }
}

总结

本文演示了如何向Weather应用程序添加更多记录类型,以及如何构建Blazor WASMServer项目来处理新类型。

在最后一篇文章中,我们将研究应用程序和部署中的一些关键概念和代码。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值