目录
如果您不知道页面在开发时的外观,应该根据用户或站点管理员可以动态更改的数据显示页面的内容和页面控件,会发生什么情况?您需要动态生成页面。
介绍
如果您问我为什么对这项技术感到如此兴奋,我会回答——好吧,因为我讨厌JavaScript(与其他许多语言一样),而这次Microsoft为Web开发人员提供了如此有用和强大的功能,现在它变成了真正的功能。 JavaScript和所有这些过时软件框架的替代品,旨在延长其毫无意义的寿命(Angular,React等)
在我当前的位置(Pro Coders)工作,我们使用了预览版的Blazor,并尝试了许多令人惊奇的事情,其中大部分是使用SignalR之上的Blazor Server-Side。我还要提到,我今天要分享的技术也可以用于客户端Blazor WebAssembly。
如果您有兴趣,还有另一篇关于动态表单的文章:
动态内容
如您所知,定义Blazor UI时,我们使用的是Razor页面——已有大约5年的技术,并且我不会浪费您的时间来研究Razor,前提是它很简单并且为您所熟知。
如果您不知道页面在开发时的外观,应该根据用户或站点管理员可以动态更改的数据显示页面的内容和页面控件,会发生什么情况?您需要动态生成页面。
让我们来谈谈现实生活中的内容管理系统中的一个示例,在该示例中,站点管理员可以决定用户可以在用户个人资料页面中填充哪些字段,并让我阐述我们今天将要实现的用户故事。
用户案例1:动态生成UI
- 根据从服务接收的控制列表生成UI页面
- 支持两种控件:TextEdit和DateEdit
- 控制列表对UI生成属性:Label,Type,Required
实现——创建项目
让我们创建一个新的Blazor项目,并使它保持简单。打开Visual Studio 2019,然后单击创建新项目,然后找到Blazor模板并单击下一步:
输入项目名称, 在下一页上选择Blazor Server App,然后单击Create:
您将看到一个为您创建的新解决方案,它将包含Visual Studio模板添加用于学习目的的多个页面。您可以通过单击播放按钮(绿色三角形)来构建和运行解决方案,以在浏览器中查看创建的应用程序。
实现——定义模型和服务
我更喜欢从定义模型开始,因此创建一个新类ControlDetails.cs 并将以下代码放入其中:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace DemoDynamicContent
{
public class ControlDetails
{
public string Type { get; set; }
public string Label { get; set; }
public bool IsRequired { get; set; }
}
}
现在,我们可以创建一个服务类,该服务类现在将返回一些测试数据,因此创建ControlService.cs 并添加以下代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace DemoDynamicContent
{
public class ControlService
{
public List<ControlDetails> GetControls()
{
var result = new List<ControlDetails>();
result.Add(new ControlDetails { Type = "TextEdit",
Label = "First Name", IsRequired = true });
result.Add(new ControlDetails { Type = "TextEdit",
Label = "Last Name", IsRequired = true });
result.Add(new ControlDetails { Type = "DateEdit",
Label = "Birth Date", IsRequired = false });
return result;
}
}
}
在此代码中,我们指定了要在动态页面上显示的三个控件:名字,姓氏和出生日期。
我们需要做的最后一件事是注册我们的服务以进行依赖注入,因此只需打开Startup.cs,找到[ ConfigureServices(IServiceCollection services)]方法并在底部添加一行代码即可,如下所示:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddSingleton<WeatherForecastService>();
// added line for ControlService
services.AddSingleton<ControlService>();
}
实现——动态页面
为了简单起见,让我们重用Counter.razor 页面(已经由Visual Studio模板创建)。我们需要删除除第一行以外的所有行,并开始添加我们自己的代码,首先使用DI注入我们的服务:
page "/counter"
@inject ControlService _controlService
现在我们需要执行_controlService并遍历返回的控件列表。
@page "/counter"
@inject ControlService _controlService
@foreach (var control in _controlService.GetControls())
{
}
对于每个控件,我们将希望显示一个标有*的标签(如果它是必需的控件):
@page "/counter"
@inject ControlService _controlService
@foreach (var control in _controlService.GetControls())
{
@if (control.IsRequired)
{
<div>@(control.Label)*</div>
}
else
{
<div>@control.Label</div>
}
}
最后一点是在switch 语句中呈现特定类型的控件:
@page "/counter"
@inject ControlService _controlService
@foreach (var control in _controlService.GetControls())
{
@if (control.IsRequired)
{
<div>@(control.Label)*</div>
}
else
{
<div>@control.Label</div>
}
@switch (control.Type)
{
case "TextEdit":
<input required="@control.IsRequired">
break;
case "DateEdit":
<input required="@control.IsRequired" type="date">
break;
}
}
实现——运行
现在,您可以编译并运行您的解决方案。在出现的浏览器窗口中,单击“计数器” 菜单项以查看结果:
实现——添加控件绑定
进一步扩展这个想法,我们将需要将生成的Razor控件绑定到Razor页面中的属性,并将其存储在Dictionary中,例如KeyisLabel和Value是Razor控件值。
在这里,我添加了具有服务执行逻辑以及绑定到控件的所有属性和事件的@code 部分。绑定是双向的。
结果代码如下所示:
@page "/counter"
@inject ControlService _controlService
@foreach (var control in ControlList)
{
@if (control.IsRequired)
{
<div>@(control.Label)*</div>
}
else
{
<div>@control.Label</div>
}
@switch (control.Type)
{
case "TextEdit":
<input @bind-value="@Values[control.Label]" required="@control.IsRequired" />
break;
case "DateEdit":
<input type="date" value="@Values[control.Label]"
@onchange="@(a => ValueChanged(a, control.Label))"
required="@control.IsRequired" />
break;
}
}
<br/>
<button @onclick="OnClick">Submit</button>
@code
{
private List<ControlDetails> ControlList;
private Dictionary<string, string> Values;
protected override async Task OnInitializedAsync()
{
ControlList = _controlService.GetControls();
Values = ControlList.ToDictionary(c => c.Label, c => "");
}
void ValueChanged(ChangeEventArgs a, string label)
{
Values[label] = a.Value.ToString();
}
string GetValue(string label)
{
return Values[label];
}
private void OnClick(MouseEventArgs e)
{
// send your Values
}
}
如果您现在运行解决方案,OnClick方法中有一个断点 ,然后在页面控件中输入值,然后单击“提交” 按钮,您将在底部的监视面板中看到输入的值:
太棒了,我们将输入的值存储在Dictionary中,现在可以将其提供给将值保存到数据库的服务。
包含结果代码的完整解决方案可以在我的GitHub页面上找到:
概括
在本练习中,我们实现了一个UI页面,该页面使用从服务接收的数据生成控件。数据控制演示。提供存储在数据库中的数据,我们向用户展示内容。如果我们要呈现稍微不同的内容——我们只需要更改数据库中的数据,并且用户可以看到我们的更改,则无需重新编译或部署。
但是此解决方案有一个小的限制——它仅支持我们razor页面的[switch]语句中指定的那些控件。每次我们需要显示未在[ switch]语句中指定的控件时,都需要对其进行扩展,并使用新的控件添加代码并重新编译解决方案。
下一个挑战——动态内容的自定义控件
是否可以创建一个外部可扩展的动态页面,该页面将支持我们以后可以在单独的程序集中添加的所有控件,而无需重新编译我们的动态页面?
https://www.codeproject.com/Articles/5282481/Microsoft-Blazor-Dynamic-Content