您当前的位置: 首页 >  ui

寒冰屋

暂无认证

  • 0浏览

    0关注

    2286博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

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

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

目录

介绍

示例项目和代码

过程概述

数据库

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] varchar NOT NULL,
	[Latitude] decimal NOT NULL,
	[Longitude] decimal NOT NULL,
	[Elevation] decimal NOT NULL
)
CREATE TABLE [dbo].[WeatherReport](
	[WeatherReportID] [int] IDENTITY(1,1) NOT NULL,
	[WeatherStationID] [int] NOT NULL,
	[Date] [smalldatetime] NULL,
	[TempMax] decimal NULL,
	[TempMin] decimal NULL,
	[FrostDays] [int] NULL,
	[Rainfall] decimal NULL,
	[SunHours] decimal NULL
)

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

注意

  1. 它们既映射ID又映射DisplayName到IDbRecord。
  2. WeatherReport映射Month和Year以允许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 Repository的SQL文件夹中使用。

CEC天气库

我们要:

  1. 为每种记录类型添加模型类。
  2. 添加一些特定于该项目的实用程序类。在这种情况下,我们
  • 添加一些扩展到decimal以正确显示我们的字段(Latitude和Longitude)。
  • 为每种记录类型的编辑器添加自定义验证器。
  • 更新WeatherForecastDBContext来处理新的记录类型。
  • 构建特定的Controller和Data Services来处理每种记录类型。
  • 为每种记录类型构建特定的列表/编辑/查看表单。
  • 更新NavMenu组件。
为记录添加模型类
  1. 我们实现了IDbRecord。
  2. 我们将SPParameter自定义属性添加到映射到存储过程的所有属性。
  3. 我们用[Not Mapped]来装饰未映射到数据库视图的属性。
// CEC.Weather/Model/DbWeatherStation.cs
public class DbWeatherStation :
    IDbRecord
{
    [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
{
    [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
    {
        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
    {
        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属性,并在OnModelCreating中modelBuilder调用。

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

public DbSet WeatherStation { get; set; }

public DbSet WeatherReport { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
......
    modelBuilder
        .Entity(eb =>
        {
            eb.HasNoKey();
            eb.ToView("vw_WeatherStation");
        });
    modelBuilder
        .Entity(eb =>
        {
            eb.HasNoKey();
            eb.ToView("vw_WeatherReport");
        });
}
添加数据和控制器服务

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

添加IWeatherStationDataService和IWeatherReportDataService接口。

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

namespace CEC.Weather.Services
{
    public interface IWeatherStationDataService : 
        IDataService
    {}
}

添加服务器数据服务。

// 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,
        IWeatherStationDataService
    {
        public WeatherStationServerDataService
               (IConfiguration configuration, IDbContextFactory 
                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,
        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, 
           IControllerService
    {
        /// 
        /// List of Outlooks for Select Controls
        /// 
        public SortedDictionary 
               OutlookOptionList => Utils.GetEnumList();

        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
    {
        [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


    
        @this.PageTitle
    
    
        
            
                
                    
                        Month/Year
                    
                    
                        
                        
                    
                    
                        Station
                    
                    
                        
                        
                    
                    
                        ID
                    
                    
                        
                        
                    
                
                
                    
                        Max Temperature ° C:
                    
                    
                        
                        
                    
                    
                        Min Temperature ° C:
                    
                    
                        
                        
                    
                
                
                    
                        Frost Days
                    
                    
                        
                        
                    
                    
                        Rainfall (mm)
                    
                    
                        
                        
                    
                    
                        Sunshine (hrs)
                    
                    
                        
                        
                    
                
            
        
        
            
                
                    
                        Exit To List
                    
                    
                        Exit
                    
                    
                        Exit
                    
                
            
        
    
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
    {
        [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


    
        @this.PageTitle
    
    
        
            
                
                    
                        
                        
                            
                                Record ID:
                            
                            
                                
                                
                            
                        
                        
                            
                                Name:
                            
                            
                                
                                
                            
                            
                                 this.Service.Record.Name) />
                            
                        
                        
                            
                                Latitude
                            
                            
                                
                                
                            
                            
                                 this.Service.Record.Latitude) />
                            
                        
                        
                            
                                Longitude
                            
                            
                                
                                
                            
                            
                                 this.Service.Record.Longitude) />
                            
                        
                        
                            
                                Elevation
                            
                            
                                
                                
                            
                            
                                 this.Service.Record.Elevation) />
                            
                        
                    
                
            
            
                
                    
                        
                    
                    
                        
                         Cancel
                        
                         Save & Exit
                        
                         Save
                        
                         Exit Without Saving
                        Exit To List
                        Exit
                    
                
            
        
    
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


    
        
            @this.ListTitle
        
        
            ID
            Name
            Latitiude
            Longitude
            Elevation
            
        
        
            
                @context.ID
                @context.Name
                @context.Latitude.AsLatitude()
                @context.Longitude.AsLongitude()
                @context.Elevation.DecimalPlaces(1)
                
            
        
        
            
                
                    
                    
                
            
        
    

// 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
    {
        /// 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(id);

        /// Method called when the user clicks on a row Edit button.
        protected void OnEdit(int id) => this.OnEditAsync(id);
    }
}
天气报告表单

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

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

    
        Station:
    
    
        
    

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

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

    public partial class WeatherReportEditorForm : 
           EditRecordComponentBase
    {
        .......
        // Property to hold the Station Lookup List
        public SortedDictionary 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();
        }
    }

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

// 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
    .....
    
  • Modal Weather
  • Weather Stations
  • Weather Reports
  • Github Repo
  • ......
    过滤器控件

    添加一个名为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 MonthLookupList { get; set; }
    
            // Year Lookup List
            private SortedDictionary YearLookupList { get; set; }
    
            // Weather Station Lookup List
            private SortedDictionary 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
                                    ("-- ALL STATIONS --");
                // Get the months in the year
                this.MonthLookupList = new SortedDictionary 
                                       { { 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 
                               { { 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
    
    
    
        
            
                @if (this.ShowID)
                {
                    
                    
                        Weather Station:
                        
    } Month:
    Year:

    过滤器显示一组下拉菜单。更改值时,将从过滤器列表中添加、更新或删除该值,并启动服务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();
        services.AddSingleton();
        services.AddSingleton();
        // Scoped service for each Controller Service
        services.AddScoped();
        services.AddScoped();
        services.AddScoped();
        // Transient service for the Fluent Validator for each record
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        // Factory for building the DBContext 
        var dbContext = configuration.GetValue("Configuration:DBContext");
        services.AddDbContextFactory
        (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
    
    
    // CEC.Blazor.Server/Routes/WeatherStation/WeatherStationListView.razor
    @page "/WeatherStation"
    @namespace CEC.Blazor.Server.Routes
    @inherits ApplicationComponentBase
    
    
    
    @code {
        public UIOptions UIOptions => new UIOptions()
        {
            ListNavigationToViewer = true,
            ShowButtons = true,
            ShowAdd = true,
            ShowEdit = true
        };
    }

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

    @page "/WeatherStation/View"
    @namespace CEC.Blazor.Server.Routes
    @inherits ApplicationComponentBase
    
    
    
        
        
    
    // CEC.Blazor.Server/Routes/WeatherReport/WeatherReportEditorView.razor
    @page "/WeatherReport/New"
    @page "/WeatherReport/Edit"
    
    @inherits ApplicationComponentBase
    
    @namespace CEC.Blazor.Server.Routes
    
    

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

    // CEC.Blazor.Server/Routes/WeatherReport/WeatherReportListView.razor
    @page "/WeatherReport"
    
    @namespace CEC.Blazor.Server.Routes
    @inherits ApplicationComponentBase
    
    
    
    
    
    @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
    
    
    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();
        services.AddScoped();
        services.AddScoped();
        // Scoped service for the Controller Services
        services.AddScoped();
        services.AddScoped();
        services.AddScoped();
        // Transient service for the Fluent Validator for the records
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        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();
        services.AddSingleton();
        services.AddSingleton();
        // Factory for building the DBContext 
        var dbContext = configuration.GetValue("Configuration:DBContext");
        services.AddDbContextFactory
        (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 logger;
    
            public WeatherStationController(ILogger logger, 
                   IWeatherStationDataService dataService)
            {
                this.DataService = dataService;
                this.logger = logger;
            }
    
            [MVC.Route("weatherstation/list")]
            [HttpGet]
            public async Task GetList() => 
                   await DataService.GetRecordListAsync();
    
            [MVC.Route("weatherStation/filteredlist")]
            [HttpPost]
            public async Task 
                   GetFilteredRecordListAsync([FromBody] FilterList filterList) => 
                   await DataService.GetFilteredRecordListAsync(filterList);
    
            [MVC.Route("weatherstation/base")]
            public async Task GetBaseAsync() => 
                   await DataService.GetBaseRecordListAsync();
    
            [MVC.Route("weatherstation/count")]
            [HttpGet]
            public async Task Count() => await DataService.GetRecordListCountAsync();
    
            [MVC.Route("weatherstation/get")]
            [HttpGet]
            public async Task GetRec(int id) => 
                   await DataService.GetRecordAsync(id);
    
            [MVC.Route("weatherstation/read")]
            [HttpPost]
            public async Task Read([FromBody]int id) => 
                   await DataService.GetRecordAsync(id);
    
            [MVC.Route("weatherstation/update")]
            [HttpPost]
            public async Task Update([FromBody]DbWeatherStation record) => 
                   await DataService.UpdateRecordAsync(record);
    
            [MVC.Route("weatherstation/create")]
            [HttpPost]
            public async Task Create([FromBody]DbWeatherStation record) => 
                   await DataService.CreateRecordAsync(record);
    
            [MVC.Route("weatherstation/delete")]
            [HttpPost]
            public async Task 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 logger;
    
            public WeatherReportController(ILogger logger, 
                   IWeatherReportDataService dataService)
            {
                this.DataService = dataService;
                this.logger = logger;
            }
    
            [MVC.Route("weatherreport/list")]
            [HttpGet]
            public async Task GetList() => 
                   await DataService.GetRecordListAsync();
    
            [MVC.Route("weatherreport/filteredlist")]
            [HttpPost]
            public async Task 
                   GetFilteredRecordListAsync([FromBody] FilterList filterList) => 
                   await DataService.GetFilteredRecordListAsync(filterList);
    
            [MVC.Route("weatherreport/distinctlist")]
            [HttpPost]
            public async Task 
                   GetDistinctListAsync([FromBody] DbDistinctRequest req) => 
                   await DataService.GetDistinctListAsync(req);
    
            [MVC.Route("weatherreport/base")]
            public async Task GetBaseAsync() => 
                   await DataService.GetBaseRecordListAsync();
    
            [MVC.Route("weatherreport/count")]
            [HttpGet]
            public async Task Count() => await DataService.GetRecordListCountAsync();
    
            [MVC.Route("weatherreport/get")]
            [HttpGet]
            public async Task GetRec(int id) => 
                                               await DataService.GetRecordAsync(id);
    
            [MVC.Route("weatherreport/read")]
            [HttpPost]
            public async Task Read([FromBody]int id) => 
                                               await DataService.GetRecordAsync(id);
    
            [MVC.Route("weatherreport/update")]
            [HttpPost]
            public async Task Update([FromBody]DbWeatherReport record) => 
                                            await DataService.UpdateRecordAsync(record);
    
            [MVC.Route("weatherreport/create")]
            [HttpPost]
            public async Task Create([FromBody]DbWeatherReport record) => 
                                            await DataService.CreateRecordAsync(record);
    
            [MVC.Route("weatherreport/delete")]
            [HttpPost]
            public async Task Delete([FromBody] 
                   DbWeatherReport record) => await DataService.DeleteRecordAsync(record);
        }
    }
    总结

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

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

    关注
    打赏
    1665926880
    查看更多评论
    立即登录/注册

    微信扫码登录

    0.1379s