赞
踩
简单来讲,Blazor旨在使用C#来替代JavaScript的Web应用程序的UI框架。其主要优势有:
Blzaor具有3中托管类型:
Blazor Server 应用程序在服务器上运行,所有处理都在服务器上完成,UI/DOM 更改通过 SignalR 连接回传给客户端。
Blazor WebAssembly应用程序在浏览器中基于WebAssembly的.NET运行时运行客户端。Blazor应用程序及其依赖项和.NET运行时被下载到浏览器中。
Blazor Hybrid 用于使用混合方法生成本机客户端应用。在 Blazor Hybrid 应用中,Razor 组件与任何其他 .NET 代码一起直接在本机应用中(而不在 WebAssembly 上)运行,并通过本地互操作通道基于 HTML 和 CSS 将 Web UI 呈现到嵌入式 Web View 控件。
/
开头@page "/"
@using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@attribute [System.ComponentModel.DataAnnotations.Schema.Table("Table")]
@implements IDisposable
@implements IAsyncDisposable
@inherits ComponentBase
@inject IAsyncDisposable varName
@layout Layout.MainLayout
@namespace myNameSpace
@typeparam T1
@typeparam T2 where T2:class
@code{}
@* 我是注释 *@
@* 代码区域 *@
@{
var a = 1;
var b = 2;
var c = a + b;
}
@* 与字符串混用 *@
<h1>C的值:@(c)个单位</h1>
@* 默认隐式调用为ToString *@
<p>@DateTime.Now</p>
<p>@DateTime.IsLeapYear(2016)</p>
@* 显式表达式 *@
<p>上一周:@(DateTime.Now-TimeSpan.FromDays(7))</p>
@* HTML自动转义 *@
<p>@("<span>Hello world</span>")</p>
<p>@((MarkupString)"<span>Hello world</span>")</p>
@* 语句块 *@ @if (true) { <span>true</span> } else { <span>false</span> } @for(var i=0;i<10;i++) { <text>这里是文本@(i)</text> } @try { throw new InvalidDataException("错误"); } catch (Exception e) { <p>@e.Message</p> } finally { <span>finally</span> }
以razor为后缀,且首字母大写,必须继承自IComponent
接口,默认继承ComponentBase
类。
如果选择Server模式则只有一个项目
如果选择其他模式,则会有两个项目BlazorApp+BlazorApp.Client
在Program.cs中设置渲染模式
...
//在此设置服务端渲染模式
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents()
.AddInteractiveWebAssemblyComponents();
...
...
//设置服务端渲染模式
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode()
.AddInteractiveWebAssemblyRenderMode()
.AddAdditionalAssemblies(typeof(Client._Imports).Assembly);
<head>、<body>
等信息,其中在body中指定了Routes
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <base href="/" /> ... <HeadOutlet /> </head> <body> <Routes /> <script src="_framework/blazor.web.js"></script> </body> </html>
Routes
中指定了整体布局MainLayout
,及其他设置<Router AppAssembly="typeof(Program).Assembly">
<Found Context="routeData">
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
<FocusOnNavigate RouteData="routeData" Selector="h1" />
</Found>
</Router>
启动时Blazor会检查Assembly属性,扫描具有RouteAttribute
的组件,<Found>
标记指定在运行时处理路由的组件RouteView 组件。 此组件接收 RouteData 对象以及来自 URI 或查询字符串的任何参数。 然后,它呈现指定的组件及其布局。 可以使用 <Found>
标记来指定默认布局,当所选组件未通过 @layout
指令指定布局时,将使用该布局。使用 <NotFound>
标记指定在不存在匹配路由时返回给用户的内容
MainLayout
中,则指定了NavMenu
和@Body
,在NavMenu
中设置了导航,可以导航到定义的page,并在设置了@Body
的地方展示@inherits LayoutComponentBase
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<article class="content px-4">
@Body
</article>
</main>
</div>
NavMenu
中设定了导航信息<div class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="counter">
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Counter
</NavLink>
</div>
NavLinkMatch.All:使用此值时,只有在链接的 href
与当前 URL 完全匹配时
NavLinkMatch.Prefix:使用此值时,当链接的 href
与当前 URL 的第一部分匹配就可以
NavLink会实现一个<a>
链接实现当跳转到指定路由时,增加active的class样式,也可以自己去设置active样式,也就是当匹配后你想设置的样式是什么,如 <NavLink class="nav-link" href="" ActiveClass="myActive" Match="NavLinkMatch.All">
,这样我的样式则为myActive。
组件可以具有参数,以供父组件控制,使用公共属性和[Parameter]特性来标记(组件参数)
CustomRazor
<h1>@Title</h1>
@code {
[Parameter]public string? Title{ set; get; }
}
<CustomRazor Title="自定义名称"/>
CustomRazor
,渲染片段必须是RenderFragment?
类型,以ChildContent
命名<h1>@Title</h1>
<p>渲染片段</p>
<p>@ChildContent</p>
@code {
[Parameter]public string? Title{ set; get; }
[Parameter] public RenderFragment? ChildContent { set; get; }
}
<CustomRazor>
我是渲染片段
</CustomRazor>
渲染片段RenderFragment可以呈现任何对象,不仅仅是字符串
<CustomRazor>
<CustomRazor>
渲染片段再次使用自定义组件
</CustomRazor>
</CustomRazor>
CustomRazor
<h1>@Title</h1>
<p>渲染片段</p>
<p>@ChildContent</p>
<p>@OtherChildContent</p>
@code {
[Parameter]public string? Title{ set; get; }
[Parameter] public RenderFragment? ChildContent { set; get; }
[Parameter] public RenderFragment? OtherChildContent { set; get; }
}
<CustomRazor>
<ChildContent>
我是第一个渲染片段
</ChildContent>
<OtherChildContent>
我是第二个渲染片段
</OtherChildContent>
</CustomRazor>
@page "/{id:int}/{name?}"
<PageTitle>Home</PageTitle>
<p>导航参数是@(Id)</p>
<p>名称是@(Name)</p>
@code{
[Parameter] public int Id { set; get; }
[Parameter] public string? Name { set; get; }
}
输入/100/tom
@page "/"
<PageTitle>Home</PageTitle>
<p>第@(Page)页,共@(Size)页</p>
@code{
[Parameter][SupplyParameterFromQuery] public int? Page { set; get; }
[Parameter][SupplyParameterFromQuery(Name ="count")] public int? Size { set; get; }
}
地址栏输入?page=1&count=100
如果子组件中还有子组件,当子组件层次比较深时,可以使用级联参数
让参数沿着层次结构向下自动传递到下级组件,在父组件中使用<CascadingValue>
将子组件进行包裹,在该标记内呈现的任何组件都能够访问传递的相关参数。
<h1>我是CustomRazor</h1>
<h1>@Title</h1>
@code {
[CascadingParameter] string? Title{ set; get; }
}
<PageTitle>Home</PageTitle>
<CascadingValue Value="@("标题")">
<CustomRazor />
</CascadingValue>
级联参数会自动匹配类型一样的值,比如上面级联参数的类型为string,如果具有多个级联参数,则会自动匹配最近的一个
<CascadingValue Value="@("外层")">
<CascadingValue Value="@("内层")">
<CustomRazor />
</CascadingValue>
</CascadingValue>
如果想要有多个级联参数,可以指定名称
<h1>我是CustomRazor</h1>
<h1>@Title1</h1>
<h1>@Title2</h1>
@code {
[CascadingParameter(Name ="Title1")] string? Title1 { set; get; }
[CascadingParameter(Name = "Title2")] string? Title2 { set; get; }
}
<PageTitle>Home</PageTitle>
<CascadingValue Name="Title1" Value="@("外层")">
<CascadingValue Name="Title2" Value="@("内层")">
<CustomRazor />
</CascadingValue>
</CascadingValue>
事件是一个EventCallback
类型,切支持泛型参数
<h3>Event</h3>
<button style="@style" @onmouseenter="MouseOver" @onmouseleave="MouseOut">按钮</button>
@code {
string style;
void MouseOver()
{
style = "font-size:30px";
}
void MouseOut()
{
style = String.Empty;
}
}
<button class="btn btn-primary" @onclick="Toggle"> @ButtonText </button> <div class="collapse @(Expand?"show":"")"> @ChildContent </div> @code { [Parameter] public RenderFragment? ChildContent { get; set; } [Parameter] public EventCallback<bool> OnToggle { get; set; } string? ButtonText => Expand ? "折叠" : "展开"; bool Expand { get; set; } async Task Toggle() { Expand = !Expand; //触发传递进来的函数 await OnToggle.InvokeAsync(Expand); } }
<h3>Event</h3> <Collapse OnToggle="Toggle"> 要显示的内容 </Collapse> <h4>@message</h4> @code { string? message; void Toggle(bool expanded) { if (expanded) { message = "内容已经展开"; } else { message = ""; } } }
模版页继承自LayoutComponentBase
,在LayoutComponentBase
中有一个属性名称为Body
的渲染片段,标识要显示的内容。在Router
组件中一般设定了默认模版页<RouteView DefaultLayout="typeof(Layout.MainLayout)" />
,也可以对不同的组件设置不同的模板页。
@inherits LayoutComponentBase
<h3>EmptyLayout</h3>
<div>@Body</div>
@page "/event"
@layout Layout.EmptyLayout @* 只能使用一次 *@
<h3>Event</h3>
。。。
使用@bind
来进行绑定
<p>
<input @bind="InputValue" @bind:event="oninput"/>
@*默认是 onchange 标识失去焦点后更新*@
</p>
<p>
<code>InputeValue</code>:@InputValue
</p>
@code{
private string? InputValue{set;get;}
}
可以使用@bing:format
来格式化字符串
使用@bind:after
,InputAfter在失去焦点触发,不支持任何参数,经常用于输入验证
<p>
<input @bind="InputValue" @bind:after="InputAfter"/>
</p>
<p>
@message
</p>
@code{
private string? InputValue{set;get;}
string? message;
void InputAfter()
{
message = "输入后得到";
}
}
@bind:after
的弊端是不能有参数,如果要含有参数则可以使用双向绑定
<p>
<input @bind:get="text" @bind:set="OnInput"/>
</p>
@code
{
string? text;
void OnInput(string value)
{
var newValue = value ?? string.Empty;
text = newValue.Length > 4 ? "Long" : newValue;
}
}
上面都是绑定的字段,如果绑定的是属性则可以直接在属性的set和get方法中进行验证操作
下拉框的绑定
<p> <label> 选择一个品牌 <select @onchange="SelectedCarsChanged"> <option value="a">A</option> <option value="b">B</option> <option value="c">C</option> <option value="d">D</option> </select> </label> </p> <p> 选择的车:@SelectedCar </p> @code{ public string?SelectedCar{get;set;} void SelectedCarsChanged(ChangeEventArgs e) { SelectedCar = e.Value?.ToString(); } }
bind
只适用于组件内部,自定义组件实现双向绑定需按如下步骤:
[Parameter] public string? Text { set; get; }
[Parameter] public EventCallback<string> TextChanged{ set; get; }
<input type="text" value="@Text" @onchange="OnChange" />
@code {
[Parameter] public string? Text { set; get; }
[Parameter] public EventCallback<string> TextChanged{ set; get; }
Task OnChange(ChangeEventArgs e)
{
Text = e.Value?.ToString();
TextChanged.InvokeAsync(Text);
return Task.CompletedTask;
}
}
@bing-
<FormControl @bind-Text="@outerText">
</FormControl>
<p>
@outerText
</p>
@code{
string? outerText;
}
当组件需要定义多个标签属性时,可以在定义对应的组件参数,但这样过于麻烦。可以借助@attributes
来实现任意参数
<input type="text" class="form-control @(Class)" @attributes="@Attributes" />
@code {
[Parameter(CaptureUnmatchedValues =true)]public Dictionary<string,object>? Attributes{ get; set; }
}
使用组件
<FormControl Attributes="@(new Dictionary<string, object>{
["Title"]="文本框",
["style"]="color:red;font-size:18px"
})">
</FormControl>
上面代码中使用了[Parameter(CaptureUnmatchedValues =true)]
,可以自动转换为键值对。下面的使用方式与上面的效果完全相同。
<FormControl title="文本框" style="color:red;font-size:18px">
</FormControl>
在web中使用<form>
元素创建表单,将input
等放入其中实现表单功能,Blazor也支持这些,但提供了更多的组件
EditForm可以支持和对象直接关联,进行双向绑定,并提供更多功能
public class WeatherForecast
{
public DateTime Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string Summary { get; set; }
}
<EditForm Model=@currentForecast>
<InputDate @bind-Value=currentForecast.Date></InputDate>
<InputNumber @bind-Value=currentForecast.TemperatureC></InputNumber>
<InputText @bind-Value=currentForecast.Summary></InputText>
</EditForm>
@{
private WeatherForecast currentForecast;
}
EditForm
具有三个在提交后运行的事件:
OnSubmit
:无论验证结果如何,只要用户提交表单,就会触发此事件。OnValidSubmit
:当用户提交表单并且他们的输入验证通过时,将触发此事件。OnInvalidSubmit
:当用户提交表单并且他们的输入验证失败时,将触发此事件。<EditForm Model="@p" onsubmit="ValidateData"> <h3>名字:</h3> <InputText @bind-Value="p.Name"></InputText> <h3>年龄:</h3> <InputNumber @bind-Value="p.Age" min="0" max="99"></InputNumber> <Input type="submit" value="提交"/> <h3>@message</h3> </EditForm> @code { Person p = new(); string message; private async Task ValidateData(EditContext editContext) { var model =(Person)editContext.Model; if (model.Age>10) { message = "大于10岁"; } } class Person { public string Name{ set; get; } public int Age{ set; get; } } }
错误信息的展示
@page "/form" @using System.ComponentModel.DataAnnotations <PageTitle>表单验证</PageTitle> <h3>表单验证</h3> <EditForm Model="Model" OnValidSubmit="SubmitValid"> <DataAnnotationsValidator /> @* 展示所有的错误信息 *@ <ValidationSummary/> <div class="row mb-3"> <label class="col-1 col-form-label">姓名:</label> <div class="col-11"> <InputText @bind-Value="Model.Name" class="form-control" /> @* 展示单个验证信息 *@ <ValidationMessage For="()=>Model.Name"/> </div> </div> <div class="row mb-3"> <label class="col-1 col-form-label">密码:</label> <div class="col-11"> <InputText @bind-Value="Model.Password" class="form-control" type="password" /> @* 展示单个验证信息 *@ <ValidationMessage For="()=>Model.Password" /> </div> </div> <button type="submit">提交</button> </EditForm> @code { class UserInfo { [Required(ErrorMessage = "名字不能为空")] public string? Name { get; set; } [Required(ErrorMessage = "密码不能为空")] public string? Password { get; set; } } UserInfo Model = new(); Task SubmitValid() { //数据库查询等操作 return Task.CompletedTask; } }
上面的案例中是在EditForm中进行提交并且验证,而有时提交是在外面。此时需要EditContext
@page "/form" @using System.ComponentModel.DataAnnotations <PageTitle>表单验证</PageTitle> <h3>表单验证</h3> <button class="btn btn-primary" @onclick=SubmitValid>提交</button> @* 定义EditContext *@ <EditForm EditContext="Context"> <DataAnnotationsValidator /> <ValidationSummary/> <div class="row mb-3"> <label class="col-1 col-form-label">姓名:</label> <div class="col-11"> <InputText @bind-Value="Model.Name" class="form-control" /> <ValidationMessage For="()=>Model.Name"/> </div> </div> <div class="row mb-3"> <label class="col-1 col-form-label">密码:</label> <div class="col-11"> <InputText @bind-Value="Model.Password" class="form-control" type="password" /> <ValidationMessage For="()=>Model.Password" /> </div> </div> </EditForm> @code { class UserInfo { [Required(ErrorMessage = "名字不能为空")] public string? Name { get; set; } [Required(ErrorMessage = "密码不能为空")] public string? Password { get; set; } } UserInfo Model = new(); //定义EditContext属性 EditContext Context { get; set; } public Form() { Context = new EditContext(Model); } Task SubmitValid() { //查询验证是否通过 bool isValid = Context.Validate(); //数据库查询等操作 return Task.CompletedTask; } }
上面案例中在EditForm内部使用了<ValidationSummary/>和<ValidationMessage/>
来显示错误信息,这些组件必须放置在EditForm内部,如果在外部自定义错误信息则可以使用Context.GetValidationMessages();
@page "/form" @using System.ComponentModel.DataAnnotations @using System.Reflection <PageTitle>表单验证</PageTitle> <h3>表单验证</h3> <button class="btn btn-primary" @onclick=SubmitValid>提交</button> @* 定义EditContext *@ <EditForm EditContext="Context"> <DataAnnotationsValidator /> <div class="row mb-3"> <label class="col-1 col-form-label">姓名:</label> <div class="col-11"> <InputText @bind-Value="Model.Name" class="form-control" /> </div> </div> <div class="row mb-3"> <label class="col-1 col-form-label">密码:</label> <div class="col-11"> <InputText @bind-Value="Model.Password" class="form-control" type="password" /> </div> </div> </EditForm> @if (Errors.Any()) { <div class="alert alert-danger"> <ul> @foreach (var message in Errors) { <li>@message</li> } </ul> </div> } @GetValidation(nameof(Model.Name)); @code { class UserInfo { [Required(ErrorMessage = "名字不能为空")] public string? Name { get; set; } [Required(ErrorMessage = "密码不能为空")] public string? Password { get; set; } } UserInfo Model = new(); //定义EditContext属性 EditContext Context { get; set; } IEnumerable<string> Errors { get; set; } = []; public Form() { Context = new EditContext(Model); } Task SubmitValid() { //查询验证是否通过 bool isValid = Context.Validate(); if (!isValid) { Errors = Context.GetValidationMessages(); return Task.CompletedTask; } //数据库查询等操作 return Task.CompletedTask; } //获得单个属性验证消息 string? GetValidation(string name) { FieldIdentifier fieldIdentifier= Context.Field(name); if (!Context.IsValid(fieldIdentifier)) { var property = Model?.GetType()?.GetProperty(fieldIdentifier.FieldName); var requiredAtr = property?.GetCustomAttribute<RequiredAttribute>(); var value = property?.GetValue(Model); if (!requiredAtr.IsValid(value)) { return requiredAtr.ErrorMessage; } } return string.Empty; } }
上面案例中,如果出现错误则文本框边框会变为红色,这是因为当有错误时会添加invalid 的css类样式,如果想自定义样式,则可使用FormCssClassProvider
public class FormCssClassProvider : Microsoft.AspNetCore.Components.Forms.FieldCssClassProvider
{
public override string GetFieldCssClass(EditContext editContext, in FieldIdentifier fieldIdentifier)
{
//如果没有任何改动
if (!editContext.IsModified())
{
return string.Empty;
}
var valid = editContext.IsValid(fieldIdentifier);
return valid ? "is-valid" : "is-invalid";
}
}
只需设置
public Form()
{
Context = new EditContext(Model);
Context.SetFieldCssClassProvider(new FormCssClassProvider());
}
此时,文本框中会加上对钩和感叹号
泛型组件类似于C#中的泛型类,使用流程同样是先定义泛型参数,然后使用
<h3>泛型组件</h3>
@typeparam TValue where TValue:struct
@typeparam TText
<p>
值是:@Value,类型是:@typeof(TValue)
</p>
<p>
值是:@Text,类型是:@typeof(TText)
</p>
@code {
[Parameter] public TValue Value { set; get; }
[Parameter] public TText Text { set; get; }
}
<Genaric TValue="int" Value="100" TText="string" Text=@outerText />
<Genaric Value="100" Text=@outerText />
@code{
string outerText="字符串";
}
<input>
的type<h3>泛型组件</h3> @typeparam TValue <input type="@InputType" value="@CurrentValue" @oninput="OnChange"/> @code { [Parameter] public TValue? Value { set; get; } [Parameter] public EventCallback<TValue?> ValueChanged { get; set; } string? CurrentValue{ set; get; } Task OnChange(ChangeEventArgs e) { var tmpValue = e.Value; if (tmpValue is null) { return Task.CompletedTask; } //转换 var newValue = Convert.ChangeType(tmpValue, typeof(TValue)); Value = (TValue)newValue; ValueChanged.InvokeAsync(Value); CurrentValue = BindConverter.FormatValue(tmpValue)?.ToString(); return Task.CompletedTask; } //判断类型 string? InputType => Value switch { double or float or int or decimal => "number", DateOnly or DateTime or DateTimeOffset => "date", _ => "text" }; }
<ul> <li> 数字: <Genaric @bind-Value="@num"/> </li> <li> 文本: <Genaric @bind-Value="@text" /> </li> <li> 时间: <Genaric @bind-Value="@time" /> </li> </ul> @code{ string text; float num; DateTime time = DateTime.Now; }
模版化组件通常和泛型组件相结合,案例:需展示数据列表,展示的形式及数据需可自定义。
@typeparam TData @if (Datas is not null) { <table class="table"> <thead> <tr>@HeaderTemplage</tr> </thead> <tbody> @foreach (var item in Datas) { <tr> @RowTemplate?.Invoke(item) </tr> } </tbody> </table> } @code { [Parameter] public IEnumerable<TData> Datas{ set; get; } [Parameter] public RenderFragment<TData>? RowTemplate { set; get; } [Parameter] public RenderFragment? HeaderTemplage { set; get; } }
上面代码中,Datas保存数据,但是TData类型不确定,在tbody中展示时,不确定里面有什么数据,所以需要用户显示方式。同样,表头thead同样也不确定需要展示哪些表头属性,需要用户来确定
<Genaric Datas="@Users"> <HeaderTemplage> <th>Id</th> <th>名称</th> </HeaderTemplage> <RowTemplate> <td>@context.Id</td> <td>@context.Name</td> </RowTemplate> </Genaric> @code{ class User { public int Id { get; set; } public string? Name { get; set; } } IEnumerable<User> Users => new List<User> { new(){ Id=1, Name="张三"}, new(){ Id=2, Name="李四"}, new(){ Id=3, Name="王五"}, new(){ Id=4, Name="赵六"} }; }
在RowTemplate
中的context
代表泛型类型,和this含义用法有些相同
名称 | 描述 | 呈现位置 | 交互 |
---|---|---|---|
静态 | 静态服务器端呈现(静态 SSR) | 服务器 | ❌否 |
交互式 Blazor Server | 使用 Blazor Server 的交互式服务器端呈现(交互式 SSR)。 | 服务器 | ✔️是 |
交互式 WebAssembly | 使用 Blazor WebAssembly 的客户端呈现 (CSR)。 | 客户端 | ✔️是 |
交互式自动 | 先使用 Blazor Server 然后使用 CSR 。 | 服务器,然后客户端 |
Blazor Server可兼容WebAssembly,反之不可以。也就是说在Server模式下的组件可放置WebAssembly组件,反之不行。
需要在Program中增加相应中间件
//在此设置服务端渲染模式
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents()
.AddInteractiveWebAssemblyComponents();
//设置服务端渲染模式
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode()
.AddInteractiveWebAssemblyRenderMode()
.AddAdditionalAssemblies(typeof(Client._Imports).Assembly);
如果在创建工程时设置了全局,则在App.razor中会自动设置渲染模式,渲染模式是向下传递的。也就是如果子组件没有设置渲染模式,则继承父组件的渲染模式。
<!DOCTYPE html>
<html lang="en">
<head>
...
<HeadOutlet @rendermode="InteractiveAuto" />
</head>
<body>
<Routes @rendermode="InteractiveAuto" />
<script src="_framework/blazor.web.js"></script>
</body>
</html>
也可在组件中直接使用@rendermode InteractiveServer
来指定渲染模式,也可在外部使用<Genaric @rendermode="InteractiveWebAssembly">
进行指定,如果在外部使用则在内部不能指定渲染模式。
当组件中含有RenderFragment
参数,这种参数不可序列化,如果指定渲染模式时会报错,遇到这种问题需要在其外层包装一下就可以
一般在app.css中进行定义,但是不利于管理
可以定义一个组件名称+.css
的文件,如GenaricTable.razor.css
注意,在App.razor中一定要引用<link rel="stylesheet" href="工程名.styles.css" />
可以定义一个组件名称+.cs
的文件,如GenaricTable.razor.cs
,并将类设置为partial
当程序遇到未捕获的异常时,会在底部弹出如下提示。
可在MainLayout中设置错误提示
<article class="content px-4">
<ErrorBoundary>
@Body
</ErrorBoundary>
</article>
默认错误提示
可自定义错误样式
<article class="content px-4">
<ErrorBoundary>
<ErrorContent>
出现错误,@context.Message
</ErrorContent>
<ChildContent>
@Body
</ChildContent>
</ErrorBoundary>
</article>
Count:@count @code { int count = 0; async Task DoCount() { for (int i = 0; i < 10; i++) { await Task.Delay(1000); count++; StateHasChanged(); } } // 页面会一直卡着,直到运行完DoCount protected override async Task OnInitializedAsync() { await DoCount(); } }
流式渲染解决了这个问题
只需要加上@attribute [StreamRendering]
即可实现
预呈现是先呈现一部分尽快输出页面的HTML UI,让用户感觉提升了响应速度。
Perrender.razor
<div class="card"> <div class="card-body"> <h2>预呈现 :@Title</h2> <hr/> <p>Hello world</p> <button class="btn btn-success">提交</button> @if (_isComplete) { <h3>渲染完成</h3> } </div> </div> @code { [Parameter] public string? Title{ set; get; } bool _isComplete; protected override async Task OnInitializedAsync() { await Task.Delay(2000); _isComplete = true; } }
第一个关闭预呈现,第二个打开预呈现
<Perrender Title="开启" @rendermode="new InteractiveWebAssemblyRenderMode(false)"/>
------------------------------
<Perrender Title="关闭" @rendermode="new InteractiveWebAssemblyRenderMode(true)" />
------------------------------
如果使用预呈现,在server模式中,需要注意状态保留问题。
<button onclick="javascript:alert('提示')">提示</button>
IJSRuntime
@inject IJSRuntime JS <button @onclick="Alert">提示</button> <button @onclick="Propmt">弹出框</button> 输入的名称是 @Value @code { async Task Alert() { //带Void的表示无返回值 //第一个参数为js的函数名,后面的参数为可变参数列表 await JS.InvokeVoidAsync("hello", "参数"); } string? Value{ set; get; } async Task Propmt() { var value = await JS.InvokeAsync<string>("prompt", "请输入名字"); Value = value; } }
在项目中wwwroot中增加js文件,并在APP.razor中引用该文件<script src="app.js"></script>
//js中定义的函数
function hello() {
alert('我是自定义hello函数');
}
在C#中调用
async Task Alert()
{
await JS.InvokeVoidAsync("hello", "参数");
}
public class Functions
{
[JSInvokable]
public static int Add()
{
return 1 + 5;
}
[JSInvokable]
public static Task<int> AddAsync()
{
return Task.FromResult(1+10);
}
}
function add() {
//参数1:C#函数所在的程序集 参数2:函数名,参数3:可变参数列表
//DotNet是固定的
let result = DotNet.invokeMethod('BlazorApp2.Client', 'Add');
console.log(result);
}
function addAsync() {
DotNet.invokeMethodAsync('BlazorApp2.Client', 'AddAsync').then(r=>console.log(r));
}
这种方式非常不推荐,如果有多个.net运行时,会导致错误
public class Functions
{
[JSInvokable]
public int Add()
{
return 1 + 5;
}
[JSInvokable]
public Task<int> AddAsync()
{
return Task.FromResult(1 + 10);
}
}
@inject IJSRuntime JS <button @onclick="Add">加</button> <button @onclick="AddAsync">加-异步</button> @code { async Task Add() { //获取引用,并调用js中定义的方法,js中需要有引用参数 var dotReference = DotNetObjectReference.Create(new Functions()); await JS.InvokeVoidAsync("add", dotReference); } async Task AddAsync() { var dotReference = DotNetObjectReference.Create(new Functions()); await JS.InvokeVoidAsync("addAsync", dotReference); } }
function add(p) {
//不需要传递程序集,p为dot引用,里面有相关函数信息
let result = p.invokeMethod('Add');
console.log(result);
}
function addAsync(p) {
p.invokeMethodAsync('AddAsync').then(r => console.log(r));
}
每个组件都是继承自ComponentBase类,完全可以自定义类来实现Razor组件的功能
public class Button : ComponentBase { [Parameter] public RenderFragment? ChildContent { get; set; } [Parameter]public bool Outline { get; set; } [Parameter] public string? Tag { get; set; } = "button"; protected override void BuildRenderTree(RenderTreeBuilder builder) { //<button></button> builder.OpenElement(0, Tag); builder.AddAttribute(1, "class", $"btn btn-{(Outline?"outline-":"")}success"); builder.AddAttribute(2, "onclick", EventCallback.Factory.Create(this, ()=>{ 。。。 })); builder.AddContent(10, ChildContent); builder.CloseElement(); } }
实现上面的类后可以像使用组件一样来使用
<Button>填充按钮</Button>
<Button Outline>边框按钮</Button>
<Button Tag="span">Span 按钮</Button>
在用Auto模式或者WebAssembly模式时,往往需要获取远程数据,这时就涉及到与Web API的交互。
工程名.Client
项目下的Program.cs中注册HTTP服务static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri("http://localhost:5041/") });
await builder.Build().RunAsync();
}
@inject HttpClient client <button @onclick=GetDataAsync>获取远程数据</button> @if (Data is null) { <div>数据加载中</div> } else { <Genaric Datas="Data"> <HeaderTemplage> <th>日期</th> <th>摄氏度</th> <th>华氏度</th> <th>说明</th> </HeaderTemplage> <RowTemplate> <td>@context.Date</td> <td>@context.TemperatureC</td> <td>@context.TemperatureF</td> <td>@context.Summary</td> </RowTemplate> </Genaric> } @code { public class WeatherForecast { public DateOnly Date { get; set; } public int TemperatureC { get; set; } public int TemperatureF { get; set; } public string? Summary { get; set; } } IEnumerable<WeatherForecast>? Data{ set; get; } async Task GetDataAsync() { Data = await client.GetFromJsonAsync<IEnumerable<WeatherForecast>>("WeatherForecast"); } }
Hosting Bundle
AspNetCoreModuleV2
aspNetCore
无托管代码
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。