Blazor系统教程(.net8)
Blazor系统教程
1.认识 Blazor
简单来讲,Blazor旨在使用C#来替代JavaScript的Web应用程序的UI框架。其主要优势有:
- 使用C#编写代码,这可提高应用开发和维护的效率利用现有的NET库生态系统
- 受益于NET的性能、可靠性和安全性与新式托管平台(如 Docker) 集成
- 以一组稳定、功能丰富且易用的通用语言、框架和工具为基础来进行生成
Blzaor具有3中托管类型:
Blazor Server
Blazor Server 应用程序在服务器上运行,所有处理都在服务器上完成,UI/DOM 更改通过 SignalR 连接回传给客户端。

Blazor WebAssembly
Blazor WebAssembly应用程序在浏览器中基于WebAssembly的.NET运行时运行客户端。Blazor应用程序及其依赖项和.NET运行时被下载到浏览器中。

Blazor Hybrid
Blazor Hybrid 用于使用混合方法生成本机客户端应用。在 Blazor Hybrid 应用中,Razor 组件与任何其他 .NET 代码一起直接在本机应用中(而不在 WebAssembly 上)运行,并通过本地互操作通道基于 HTML 和 CSS 将 Web UI 呈现到嵌入式 Web View 控件。

2.Razor语法和指令
指令
- 路由定义,可以定义多个,但是不能重复,必须以
/
开头
@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>
}
3. Razo组件
以razor为后缀,且首字母大写,必须继承自IComponent
接口,默认继承ComponentBase
类。
4.项目结构和路由组件
如果选择Server模式则只有一个项目
如果选择其他模式,则会有两个项目BlazorApp+BlazorApp.Client
在Program.cs中设置渲染模式
...
//在此设置服务端渲染模式
builder.Services.AddRazorComponents().AddInteractiveServerComponents().AddInteractiveWebAssemblyComponents();
...
...
//设置服务端渲染模式
app.MapRazorComponents<App>().AddInteractiveServerRenderMode().AddInteractiveWebAssemblyRenderMode().AddAdditionalAssemblies(typeof(Client._Imports).Assembly);
- App.razor为根网页,里面有
<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。
5. 组件参数
组件参数
组件可以具有参数,以供父组件控制,使用公共属性和[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>
6. 导航参数和查询参数
导航参数
@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
7. 级联参数
如果子组件中还有子组件,当子组件层次比较深时,可以使用级联参数
让参数沿着层次结构向下自动传递到下级组件,在父组件中使用<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>
8. 事件和事件参数
事件是一个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;}
}
- 自定义事件
- 首先定义一个Collapse.Razor,在该Razor中定义EventCallback类型的属性
<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);}
}
- 使用定义的Razor
<h3>Event</h3><Collapse OnToggle="Toggle">要显示的内容
</Collapse>
<h4>@message</h4>
@code {string? message;void Toggle(bool expanded){if (expanded){message = "内容已经展开";}else{message = "";}}
}
9. 模板页
模版页继承自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>
。。。
10. 单向绑定和双向绑定
使用@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();}
}
11. 自定义组件实现双向绑定
bind
只适用于组件内部,自定义组件实现双向绑定需按如下步骤:
- 定义绑定属性值
[Parameter] public string? Text { set; get; }
- 定义一个EventCallback泛型类型的属性,名称必须为第一步定义的属性值+Changed
[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;
}
12. 组件的任意参数
当组件需要定义多个标签属性时,可以在定义对应的组件参数,但这样过于麻烦。可以借助@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>
13. 表单和验证
在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;}
}
14. 表单验证的进阶
在EditForm外面提交验证
上面的案例中是在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;}
}
FormCssClassProvider
上面案例中,如果出现错误则文本框边框会变为红色,这是因为当有错误时会添加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());
}
此时,文本框中会加上对钩和感叹号
15. 组件的生命周期
16. 泛型组件
基本使用
泛型组件类似于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
- 泛型组件Genaric.razor
<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;
}
17. 模板化组件
模版化组件通常和泛型组件相结合,案例:需展示数据列表,展示的形式及数据需可自定义。
@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含义用法有些相同
18. 渲染模式
名称 | 描述 | 呈现位置 | 交互 |
---|---|---|---|
静态 | 静态服务器端呈现(静态 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
参数,这种参数不可序列化,如果指定渲染模式时会报错,遇到这种问题需要在其外层包装一下就可以
19. CSS隔离和代码隔离
CSS隔离
一般在app.css中进行定义,但是不利于管理
可以定义一个组件名称+.css
的文件,如GenaricTable.razor.css
注意,在App.razor中一定要引用<link rel="stylesheet" href="工程名.styles.css" />
代码隔离
可以定义一个组件名称+.cs
的文件,如GenaricTable.razor.cs
,并将类设置为partial
20. 异常处理
当程序遇到未捕获的异常时,会在底部弹出如下提示。
可在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>
21. 流式渲染
Count:@count@code {int count = 0;async Task DoCount(){for (int i = 0; i < 10; i++){await Task.Delay(1000);count++;StateHasChanged();}}// 页面会一直卡着,直到运行完DoCountprotected override async Task OnInitializedAsync(){await DoCount();}
}
流式渲染解决了这个问题
只需要加上@attribute [StreamRendering]
即可实现
22. 预呈现模式
预呈现是先呈现一部分尽快输出页面的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模式中,需要注意状态保留问题。
23. C# 和 JS 的互操作
C#调用JS
直接写js
<button onclick="javascript:alert('提示')">提示</button>
使用IJSRuntime
- 注入
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;}
}
调用自定义JS函数
在项目中wwwroot中增加js文件,并在APP.razor中引用该文件<script src="app.js"></script>
//js中定义的函数
function hello() {alert('我是自定义hello函数');
}
在C#中调用
async Task Alert()
{await JS.InvokeVoidAsync("hello", "参数");
}
JS调用C#中的函数
静态方法
- 用C#写静态方法
public class Functions
{[JSInvokable]public static int Add(){return 1 + 5;}[JSInvokable]public static Task<int> AddAsync(){return Task.FromResult(1+10);}
}
- js调用写的静态方法
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);}
}
- 在razor页面中定义方法
@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);}
}
- js中调用C#中的方法
function add(p) {//不需要传递程序集,p为dot引用,里面有相关函数信息let result = p.invokeMethod('Add');console.log(result);
}function addAsync(p) {p.invokeMethodAsync('AddAsync').then(r => console.log(r));
}
24. 渲染树
每个组件都是继承自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>
25. 与 WEB API 的交互
在用Auto模式或者WebAssembly模式时,往往需要获取远程数据,这时就涉及到与Web API的交互。
- 首先要确定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();
}
- WebAPI的返回形式一般是Json数据,首先声明对应Json数据的类,然后注入HttpClient。并利用上文中模版化组件进行展示
@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");}
}
26. 部署到 IIS
- .net Core运行时下载
Hosting Bundle
- 设置发布路径,Ip、端口号等
- 确认设置模块中是否含有
AspNetCoreModuleV2
- 确认处理程序映射是否含有
aspNetCore
- 设置应用程序池,将.net Clr中.net CLR版本设置为
无托管代码
相关文章:

Blazor系统教程(.net8)
Blazor系统教程 1.认识 Blazor 简单来讲,Blazor旨在使用C#来替代JavaScript的Web应用程序的UI框架。其主要优势有: 使用C#编写代码,这可提高应用开发和维护的效率利用现有的NET库生态系统受益于NET的性能、可靠性和安全性与新式托管平台(如…...

Day15:技术架构、Maven、Spring Initializer、Spring全家桶、Spring IoC
侧重于服务端(后端),不在意前端,了解一些前端即可) 技术架构 (把Spring设计的更简单好用了就是Spring Boot) 开发环境(Maven) Maven maven通过brew安装的目录为&#x…...

[c/c++] const
const 和 #define 的区别 ? const 和指针一块出现的时候,到底谁不能修改 ? const 和 volatile 能同时修饰一个变量吗 ? const 在 c 中的作用 ? 1 const 和 #define 的区别 const 和 #define 的相同点: (1) 常数 const 和 #define 定…...

生成商品条码
php生成商品条码,编码格式为:EAN13 下载第三方包:composer require codeitnowin/barcode 生成条码代码: $filename \Str::random(40) . .png;$barcode new BarcodeGenerator();$barcode->setText($barCode);$barcode->s…...

langchain学习笔记(十一)
关于langchain中的memory,即对话历史(message history) 1、 Add message history (memory) | 🦜️🔗 Langchain RunnableWithMessageHistory,可用于任何的chain中添加对话历史,将以下之一作为…...

LabVIEW高温摩擦磨损测试系统
LabVIEW高温摩擦磨损测试系统 介绍了一个基于LabVIEW的高温摩擦磨损测试系统的软件开发项目。该系统实现高温条件下材料摩擦磨损特性的自动化测试,通过精确控制和数据采集,为材料性能研究提供重要数据支持。 项目背景 随着材料科学的发展,…...

基于YOLOv5的驾驶员疲劳驾驶行为检测系统
💡💡💡本文主要内容:详细介绍了疲劳驾驶行为检测整个过程,从数据集到训练模型到结果可视化分析。 博主简介 AI小怪兽,YOLO骨灰级玩家,1)YOLOv5、v7、v8优化创新,轻松涨点和模型轻量…...

融合软硬件串流多媒体技术的远程控制方案
远程技术已经发展得有相当水平了,在远程办公,云游戏,云渲染等领域有相当多的应用场景,以向日葵,todesk rustdesk等优秀产品攻城略地,估值越来越高。占据了通用应用的方方面面。 但是细分市场,还…...

Spring中的数据校验---JSR303
介绍–什么是JSR303 JSR 303是Java中的一项规范,用于定义在Java应用程序中执行数据校验的元数据模型和API。JSR 303的官方名称是"Bean Validation",它提供了一种在Java对象级别上执行验证的方式,通常用于确保输入数据的完整性和准…...

“揭秘网络握手与挥别:TCP三次握手和四次挥手全解析“
前言 在计算机网络中,TCP(传输控制协议)是一种重要的通信协议,用于在网络中的两台计算机之间建立可靠的连接并交换数据。TCP协议通过“三次握手”和“四次挥手”的过程来建立和终止连接,确保数据的准确传输。 一、三…...

Java开发工程师面试题(Spring)
一、Spring Bean的生命周期 生命周期可以分为以下几步: 通过Spring框架的beanFactory工厂利用反射机制创建bean对象。根据set方法或者有参构造方法给bean对象的属性进行依赖注入。判断当前bean对象是否实现相关aware接口,诸如beanNameAware、beanFactor…...

【C++】string类的基础操作
💗个人主页💗 ⭐个人专栏——C学习⭐ 💫点击关注🤩一起学习C语言💯💫 目录 导读 1. 基本概述 2. string类对象的常见构造 3. string类对象的容量操作 4. string类对象的访问及遍历操作 5. 迭代器 6.…...

Java项目:40 springboot月度员工绩效考核管理系统009
作者主页:源码空间codegym 简介:Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 本系统的功能分为管理员和员工两个角色 管理员的功能有: (1)个人中心管理功能,添加管理员账号…...

opengl 学习(三)-----着色器
着色器 分类demo效果解析教程 分类 OPengl C demo #include "glad/glad.h" #include "glfw3.h" #include <iostream> #include <cmath> #include <vector>#include <string> #include <fstream> #include <sstream>…...

电销平台架构的演变与升级
简介 信也科技电销平台承载了公司400多坐席的日常外呼任务,随着公司业务规模不断增长,业务复杂度不断提升,营销模式需要多样化,营销流程需要更加灵活。为了更好地赋能业务、提高客户转化率,电销平台不断升级优化&#…...

轻薄蓝牙工牌室内人员定位应用
在现代化企业管理的背景下,轻薄蓝牙工牌人员定位应用逐渐崭露头角,成为提升企业效率和安全性的重要工具。本文将从轻薄蓝牙工牌的定义、特点、应用场景以及未来发展趋势等方面,对其进行全面深入的探讨。 一、轻薄蓝牙工牌的定义与特点 轻薄…...

好物周刊#46:在线工具箱
https://github.com/cunyu1943 村雨遥的好物周刊,记录每周看到的有价值的信息,主要针对计算机领域,每周五发布。 一、项目 1. twelvet 一款基于 Spring Cloud Alibaba 的权限管理系统,集成市面上流行库,可以作用为快…...

20240306-1-大数据的几个面试题目
面试题目 1. 相同URL 题目: 给定a、b两个文件,各存放50亿个url,每个url各占64字节,内存限制是4G,让你找出a、b文件共同的url? 方案1:估计每个文件的大小为50G64320G,远远大于内存限制的4G。所以…...

Vue中如何处理用户权限?
在前端开发中,处理用户权限是非常重要的一个方面。Vue作为一种流行的前端框架,提供了很多便捷的方式来管理用户权限。本文将介绍一些Vue中处理用户权限的方法 1. 使用路由守卫 Vue Router提供了一个功能强大的功能,即导航守卫(N…...

【STM32】HAL库 CubeMX教程---基本定时器 定时
目录 一、基本定时器的作用 二、常用型号的TIM时钟频率 三、CubeMX配置 四、编写执行代码 实验目标: 通过CUbeMXHAL,配置TIM6,1s中断一次,闪烁LED。 一、基本定时器的作用 基本定时器,主要用于实现定时和计数功能…...

2024年最新整理腾讯云学生服务器价格、续费和购买流程
2024年腾讯云学生服务器优惠活动「云校园」,学生服务器优惠价格:轻量应用服务器2核2G学生价30元3个月、58元6个月、112元一年,轻量应用服务器4核8G配置191.1元3个月、352.8元6个月、646.8元一年,CVM云服务器2核4G配置842.4元一年&…...

【QT】重载的信号槽/槽函数做lambda表达式
重载的信号槽 函数指针: int fun(int a,long b) int (*funp)(int, long) fun; 实现回调函数就需要函数指针 信号重载 派生类槽函数发送两个信号 派生类给父类发两个信号 void (SubWidget::*mysigsub)() &SubWidget::sigSub;connect(&subw,mysigsub,t…...

C++之类(一)
1,封装 1.1 封装的引用 封装是C面向对象三大特性之一 封装的意义: 将属性和行为作为一个整体,表现生活中的事物 将属性和行为加以权限控制 1.1.1 封装意义一: 在设计类的时候,属性和行为写在一起,表…...

【工具类】repo是什么,repo常用命令,repo和git和git-repo的关系
1. repo 1. repo 1.1. repo是什么1.2. 安装1.3. repo 命令 1.3.1. repo help1.3.2. repo init1.3.3. repo sync1.3.4. repo upload1.3.5. repo start1.3.6. repo forall 1.4. mainfest 文件1.5. git-repo简介(非android repo)1.6. 参考资料 1.1. repo是什么 Repo 是一个 go…...

Java中可以实现的定时任务策略
Java中可以实现的定时任务策略 文章目录 Java中可以实现的定时任务策略自定义独立线程JDK提供的调度线程池-**ScheduledExecutorService**内核是Spring的Task执行调度quartz调度 #mermaid-svg-mQ9rPqk0Ds3ULnvD {font-family:"trebuchet ms",verdana,arial,sans-seri…...

【目标分类图像增强方法】
图像增强方法及其原理 目标分类图像增强是一种用于提高深度学习模型泛化能力的技术,通过在训练过程中对原始图像进行各种变换来增加模型所见数据的多样性。以下是几种常见的图像增强方法及其原理: 几何变换: 旋转(Rotation&#…...

游戏盾如何应对微商城网站DDoS攻击
游戏盾如何应对微商城网站DDoS攻击?随着电子商务的快速发展,微商城网站已成为众多商家开展在线业务的重要平台。然而,与此同时,网络安全威胁也愈发严重。其中,分布式拒绝服务(DDoS)攻击是一种常…...

安卓手机如何使用JuiceSSH实现公网远程连接本地Linux服务器
文章目录 1. Linux安装cpolar2. 创建公网SSH连接地址3. JuiceSSH公网远程连接4. 固定连接SSH公网地址5. SSH固定地址连接测试 处于内网的虚拟机如何被外网访问呢?如何手机就能访问虚拟机呢? cpolarJuiceSSH 实现手机端远程连接Linux虚拟机(内网穿透,手机端连接Linux虚拟机) …...

钉钉群内自定义机器人发送消息功能实现
文章目录 钉钉群内自定义机器人发送消息功能实现1、设置webhook自定义机器人2、查看官方文档,使用open api3、编写业务代码4、发送成功结果如下 钉钉群内自定义机器人发送消息功能实现 1、设置webhook自定义机器人 设置关键词 添加完成后,获得改机器人的…...

网站维护3年15000元,贵不贵?市场价多少
一般来说,给公司做好网站上线之后,网站就进入了运维期间,某功力公司给客户收费3年15000元网站运维费用,到底高不高呢? 首先,来看看网站运维都有哪些项目 网站运维涉及多个项目和任务,包括但不限…...