目录
介绍
示例项目和代码
过程概述
数据库
CEC天气库
为记录添加模型类
添加一些实用程序类
更新WeatherForecastDbContext
添加数据和控制器服务
表单
WeatherStation查看器表单
WeatherStation编辑器表单
WeatherStation列表表单
天气报告表单
导航菜单
过滤器控件
CEC.Blazor.Server
Startup.cs
气象站路由/视图
CEC.Blazor.WASM.Client
program.cs
气象站路由/视图
CEC.Blazor.WASM.Server
Startup.cs
气象站控制器
总结
介绍这是该系列的第六篇文章,并逐步向Weather Application中添加新记录。
- 项目结构与框架
- 服务——构建CRUD数据层
- View组件——UI中的CRUD编辑和查看操作
- UI组件——构建HTML / CSS控件
- View组件-UI中的CRUD列表操作
- 逐步详细介绍如何向应用程序添加气象站和气象站数据
该练习的目的是从英国气象局导入站数据。解决方案中包含一个命令行导入程序项目,用于获取和导入数据——查看代码以查看其工作方式。数据采用的是英国气象站自1928年以来的月度记录形式。我们将添加两种记录类型:
- 气象站
- 气象站报告
并且所有基础结构都为这两个记录提供UI CRUD操作。
在构建服务器和WASM部署时,我们有4个项目要添加代码:
- CEC.Weather ——共享项目库
- CEC.Blazor.Server ——服务器项目
- CEC.Blazor.WASM.Client ——WASM项目
- CEC.Blazor.WASM.Server ——WASM项目的API服务器
大部分代码是CEC.Weather中的库代码。
示例项目和代码基本代码在CEC.Blazor GitHub存储库中。
本文的完整代码位于CEC.Weather GitHub Repository中。
过程概述- 将表、视图和存储过程添加到数据库
- 将模型、服务和表单添加到CEC.Weather库中
- 添加视图并在Blazor.CEC.Server项目中配置服务。
- 添加视图并在Blazor.CEC.WASM.Client项目中配置服务。
- 添加控制器并在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
)
为每种记录类型添加视图。
注意
- 它们既映射ID又映射DisplayName到IDbRecord。
- WeatherReport映射Month和Year以允许SQL Server在这些字段上进行过滤。
- 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文件夹中使用。
我们要:
- 为每种记录类型添加模型类。
- 添加一些特定于该项目的实用程序类。在这种情况下,我们
- 添加一些扩展到decimal以正确显示我们的字段(Latitude和Longitude)。
- 为每种记录类型的编辑器添加自定义验证器。
- 更新WeatherForecastDBContext来处理新的记录类型。
- 构建特定的Controller和Data Services来处理每种记录类型。
- 为每种记录类型构建特定的列表/编辑/查看表单。
- 更新NavMenu组件。
- 我们实现了IDbRecord。
- 我们将SPParameter自定义属性添加到映射到存储过程的所有属性。
- 我们用[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更新。
现在所有共享代码都已完成,我们需要移至实际项目。
要设置服务器,我们需要
- 配置正确的服务——特定于服务器。
- 为每种记录类型构建视图——这些视图与WASM客户端中使用的视图相同。
我们需要用新的服务来更新启动,通过更新在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
要设置客户端,我们需要
- 配置正确的服务——特定于客户端。
- 为每种记录类型构建视图——与服务器相同。
我们需要使用新服务来更新程序。我们通过在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.ServerWASM服务器是API提供程序。我们要:
- 配置正确的服务。
- 为每种记录类型构建控制器。
我们需要使用新服务来更新启动。我们通过在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项目来处理新类型。
在最后一篇文章中,我们将研究应用程序和部署中的一些关键概念和代码。