VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > c#编程 >
  • C#网络编程之WebAssembly网络编程(Wasm、Blazor)

第77章 WebAssembly网络编程(Wasm、Blazor)
一、我踩过的坑:Blazor Wasm中用HttpClient直接请求后端,浏览器报“跨域被拦截”
第一次做Blazor Wasm项目时,直接在组件里用HttpClient.GetAsync("https://api.mydomain.com/data"),浏览器控制台直接报“Access-Control-Allow-Origin”错误——查了半天才知道,Wasm运行在浏览器沙箱里,受同源策略限制,后端必须vb.net教程C#教程python教程SQL教程access 2010教程配置CORS(跨域资源共享)才能访问!还有一次想在Wasm里用Socket类直接连TCP服务器,结果编译报错,因为Wasm不支持直接访问系统级Socket API,只能通过浏览器提供的HTTP、WebSocket等API。这节我把这些真实坑点揉进去,用大白话讲透Wasm的网络限制,结合Blazor Wasm的代码逐行拆解,拓展生产级优化技巧,让你一次搞定WebAssembly网络编程!
二、WebAssembly核心概念大白话:运行在浏览器里的“.NET虚拟机”

  1. 什么是WebAssembly(Wasm)?
    大白话:Wasm是一种二进制指令集,像一个轻量级的虚拟机,运行在浏览器里,和JS平级,但比JS快10-100倍(因为是编译型,JS是解释型);
    .NET与Wasm:Blazor Wasm是把.NET代码编译成Wasm二进制文件,在浏览器里运行,用C#写前端代码,不用写JS;
    沙箱限制:Wasm运行在浏览器沙箱里,不能直接访问系统资源(比如文件、Socket、CPU),只能通过浏览器提供的API(比如fetch、WebSocket)访问网络。

  2. Blazor Wasm的网络底层原理
    HttpClient:Blazor Wasm的HttpClient不是直接调用系统Socket,而是调用浏览器的fetch API,受同源策略限制;
    WebSocket:Blazor Wasm的ClientWebSocket调用浏览器的WebSocket API,支持双向通信,不受同源策略限制(但后端要配置CORS);
    JS互操作:如果浏览器API满足不了需求,比如要做P2P通信,可以通过JS互操作调用浏览器的WebRTC API。
    三、Blazor Wasm HTTP编程:从跨域配置到缓存优化

  3. 我踩过的坑:直接用HttpClient请求后端,跨域被拦截
    错误写法:不配置CORS,直接请求不同域名的后端
    csharp
    // Blazor组件中的错误代码
    @inject HttpClient Http
    @code {
    private List data;

    protected override async Task OnInitializedAsync()
    {
    // 浏览器会报跨域错误,因为前端域名是localhost:5000,后端是localhost:5001
    data = await Http.GetFromJsonAsync<List>("https://localhost:5001/api/data");
    }
    }
    正确写法:后端配置CORS,前端用HttpClient请求

  4. 后端ASP.NET Core配置CORS
    csharp

	// 后端Program.cs
	var builder = WebApplication.CreateBuilder(args);
	
	// 添加CORS服务
	builder.Services.AddCors(options =>
	{
	options.AddPolicy("AllowBlazorWasm", policy =>
	{
	// 允许前端域名(比如localhost:5000)跨域请求
	policy.WithOrigins("https://localhost:5000")
	.AllowAnyHeader()
	.AllowAnyMethod()
	.AllowCredentials(); // 允许携带Cookie
	});
	});
	
	builder.Services.AddControllers();
	var app = builder.Build();
	
	// 启用CORS中间件,要在UseRouting之后,UseAuthorization之前
	app.UseCors("AllowBlazorWasm");
	
	app.UseAuthorization();
	app.MapControllers();
	app.Run("https://localhost:5001");
  1. 前端Blazor Wasm配置HttpClient
    razor
	<!-- Blazor组件Pages/Index.razor -->
	@page "/"
	@inject HttpClient Http
	@inject NavigationManager NavManager
	
	<h1>Blazor Wasm HTTP请求示例</h1>
	
	@if (data == null)
	{
	<p>加载中...</p>
	}
	else
	{
	<ul>
	@foreach (var item in data)
	{
	<li>@item.Name: @item.Value</li>
	}
	</ul>
	}
	
	@code {
	private List<DataItem> data;
	
	protected override async Task OnInitializedAsync()
	{
	try
	{
	// 1. 用相对路径请求,避免硬编码域名,方便部署
	string apiUrl = NavManager.BaseUri + "api/data";
	// 2. 用GetFromJsonAsync,自动反序列化JSON
	data = await Http.GetFromJsonAsync<List<DataItem>>(apiUrl);
	}
	catch (HttpRequestException ex)
	{
	Console.WriteLine($"HTTP请求错误:{ex.Message}");
	}
	}
	
	// 定义数据模型,和后端返回的JSON结构一致
	private class DataItem
	{
	public string Name { get; set; }
	public int Value { get; set; }
	}
	}

代码逐行拆解(结合浏览器同源策略)

  1. 同源策略大白话
    同源:域名、协议、端口都相同,比如https://localhost:5000和https://localhost:5000是同源,https://localhost:5000和https://localhost:5001是不同源;
    跨域:不同源的请求,浏览器会拦截,除非后端配置CORS允许前端域名访问;
    生产级技巧:生产环境不要用AllowAnyOrigin,要指定具体的前端域名,避免安全风险。
  2. Blazor Wasm HttpClient的底层实现
    csharp
data = await Http.GetFromJsonAsync<List<DataItem>>(apiUrl);

底层原理:Blazor Wasm的HttpClient封装了浏览器的fetch API,不是直接调用系统Socket,所以受浏览器沙箱限制;
不支持的特性:HttpCompletionOption.ResponseHeadersRead(因为fetch API不支持流式读取响应头)、Timeout(浏览器fetch API不支持超时,Blazor Wasm的Timeout是模拟的,会取消请求但不会中断网络连接);
拓展知识:用IHttpClientFactory创建HttpClient,避免频繁创建和销毁HttpClient导致的Socket泄漏(虽然Blazor Wasm的HttpClient是基于fetch,但还是推荐用工厂模式)。
2. Blazor Wasm HTTP缓存优化:减少重复请求,提升性能
我踩过的坑:每次刷新页面都重新请求数据,加载慢
正确写法:用浏览器本地存储缓存数据
csharp

	@inject HttpClient Http
	@inject NavigationManager NavManager
	@inject IJSRuntime JsRuntime
	
	@code {
	private List<DataItem> data;
	
	protected override async Task OnInitializedAsync()
	{
	// 1. 先从本地存储读取缓存
	string cachedData = await JsRuntime.InvokeAsync<string>("localStorage.getItem", "dataCache");
	if (!string.IsNullOrEmpty(cachedData))
	{
	data = JsonSerializer.Deserialize<List<DataItem>>(cachedData);
	Console.WriteLine("从本地存储加载缓存数据");
	}
	else
	{
	// 2. 缓存不存在,请求后端
	string apiUrl = NavManager.BaseUri + "api/data";
	data = await Http.GetFromJsonAsync<List<DataItem>>(apiUrl);
	// 3. 把数据缓存到本地存储,过期时间1小时
	await JsRuntime.InvokeVoidAsync("localStorage.setItem", "dataCache", JsonSerializer.Serialize(data));
	await JsRuntime.InvokeVoidAsync("localStorage.setItem", "dataCacheExpire", DateTime.Now.AddHours(1).ToString());
	Console.WriteLine("从后端加载数据并缓存");
	}
	}
	}

代码逐行拆解(结合浏览器存储)

  1. 浏览器本地存储的类型
    localStorage:永久存储,除非用户手动清除,适合缓存不经常变的数据;
    sessionStorage:会话存储,关闭浏览器后清除,适合缓存会话数据;
    IndexedDB:大容量存储,适合缓存大文件(比如图片、视频);
    生产级技巧:缓存时要加过期时间,避免缓存数据过期,比如上面的代码加了1小时的过期时间,下次加载时先检查是否过期。
    四、Blazor Wasm WebSocket编程:从双向通信到实时聊天
  2. 我踩过的坑:Blazor Wasm的ClientWebSocket不能直接连接ws://localhost:8080,跨域被拦截
    正确写法:后端配置CORS,前端用ClientWebSocket连接
  3. 后端ASP.NET Core配置WebSocket和CORS
    csharp
	// 后端Program.cs
	var builder = WebApplication.CreateBuilder(args);
	
	builder.Services.AddCors(options =>
	{
	options.AddPolicy("AllowBlazorWasm", policy =>
	{
	policy.WithOrigins("https://localhost:5000")
	.AllowAnyHeader()
	.AllowAnyMethod()
	.AllowCredentials();
	});
	});
	
	var app = builder.Build();
	
	app.UseCors("AllowBlazorWasm");
	
	// 配置WebSocket中间件
	app.UseWebSockets();
	
	// 处理WebSocket连接
	app.Map("/ws", async context =>
	{
	if (!context.WebSockets.IsWebSocketRequest)
	{
	context.Response.StatusCode = StatusCodes.Status400BadRequest;
	return;
	}
	
	using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
	var buffer = new byte[1024 * 1024];
	WebSocketReceiveResult result;
	
	do
	{
	result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
	string message = Encoding.UTF8.GetString(buffer, 0, result.Count);
	Console.WriteLine($"收到消息:{message}");
	
	// 发送响应
	string response = $"收到消息:{message},当前时间:{DateTime.Now}";
	byte[] responseBytes = Encoding.UTF8.GetBytes(response);
	await webSocket.SendAsync(new ArraySegment<byte>(responseBytes), result.MessageType, result.EndOfMessage, CancellationToken.None);
	} while (!result.CloseStatus.HasValue);
	
	await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
	});
	
	app.Run("https://localhost:5001");
  1. 前端Blazor Wasm用ClientWebSocket连接
    razor
	<!-- Blazor组件Pages/Chat.razor -->
	@page "/chat"
	@inject IJSRuntime JsRuntime
	@implements IAsyncDisposable
	
	<h1>Blazor Wasm WebSocket聊天</h1>
	
	<input type="text" @bind="message" placeholder="输入消息" />
	<button @onclick="SendMessage">发送</button>
	
	<div>
	<h3>聊天记录</h3>
	@foreach (var msg in messages)
	{
	<p>@msg</p>
	}
	</div>
	
	@code {
	private ClientWebSocket webSocket;
	private string message;
	private List<string> messages = new List<string>();
	private CancellationTokenSource cts = new CancellationTokenSource();
	
	protected override async Task OnInitializedAsync()
	{
	// 1. 创建ClientWebSocket
	webSocket = new ClientWebSocket();
	// 2. 配置WebSocket选项,比如设置子协议
	webSocket.Options.AddSubProtocol("chat");
	// 3. 连接后端WebSocket服务,注意用wss://(HTTPS对应的WebSocket协议)
	string wsUrl = "wss://localhost:5001/ws";
	try
	{
	await webSocket.ConnectAsync(new Uri(wsUrl), cts.Token);
	messages.Add("连接WebSocket服务器成功");
	// 4. 启动后台任务接收消息
	_ = ReceiveMessagesAsync();
	}
	catch (Exception ex)
	{
	messages.Add($"连接失败:{ex.Message}");
	}
	}
	
	private async Task ReceiveMessagesAsync()
	{
	var buffer = new byte[1024 * 1024];
	while (webSocket.State == WebSocketState.Open)
	{
	try
	{
	// 5. 接收消息
	var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), cts.Token);
	if (result.MessageType == WebSocketMessageType.Close)
	{
	await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "关闭连接", cts.Token);
	messages.Add("服务器关闭连接");
	break;
	}
	string message = Encoding.UTF8.GetString(buffer, 0, result.Count);
	messages.Add($"服务器:{message}");
	// 6. 通知UI更新
	StateHasChanged();
	}
	catch (Exception ex)
	{
	messages.Add($"接收消息错误:{ex.Message}");
	break;
	}
	}
	}
	
	private async Task SendMessage()
	{
	if (string.IsNullOrEmpty(message) || webSocket.State != WebSocketState.Open)
	{
	return;
	}
	try
	{
	byte[] messageBytes = Encoding.UTF8.GetBytes(message);
	await webSocket.SendAsync(new ArraySegment<byte>(messageBytes), WebSocketMessageType.Text, true, cts.Token);
	messages.Add($"我:{message}");
	message = string.Empty;
	}
	catch (Exception ex)
	{
	messages.Add($"发送消息错误:{ex.Message}");
	}
	}
	
	// 7. 释放资源,关闭WebSocket连接
	public async ValueTask DisposeAsync()
	{
	cts.Cancel();
	if (webSocket.State == WebSocketState.Open)
	{
	await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "页面关闭", CancellationToken.None);
	}
	webSocket.Dispose();
	cts.Dispose();
	}
	}

代码逐行拆解(结合浏览器WebSocket API)

  1. WebSocket协议的类型
    ws://:对应HTTP,不安全,适合开发环境;
    wss://:对应HTTPS,安全,适合生产环境,浏览器会加密传输数据;
    生产级技巧:生产环境必须用wss://,避免数据被窃听。
  2. Blazor Wasm ClientWebSocket的限制
    不能设置超时:浏览器的WebSocket API不支持超时,Blazor Wasm的ClientWebSocket也不支持;
    不能直接访问底层Socket:ClientWebSocket是封装了浏览器的WebSocket API,不能设置Socket选项(比如NoDelay);
    拓展知识:用JS互操作调用浏览器的WebSocket API,可以实现更多功能,比如设置心跳检测,避免连接被服务器断开。
    五、Wasm与JS互操作的网络优化:从Web Worker到P2P通信
  3. 用Web Worker处理网络请求,避免阻塞UI
    我踩过的坑:大文件上传时,UI卡顿,因为网络请求在主线程执行
    正确写法:用JS Web Worker处理网络请求,Blazor Wasm通过JS互操作调用
  4. 创建JS Web Worker文件(wwwroot/js/uploadWorker.js)
    javascript
	// Web Worker文件,在后台线程执行网络请求
	self.onmessage = async (e) => {
	const file = e.data;
	const formData = new FormData();
	formData.append('file', file);
	
	try {
	const response = await fetch('https://localhost:5001/api/upload', {
	method: 'POST',
	body: formData
	});
	const result = await response.json();
	// 把结果发送回主线程
	self.postMessage({ success: true, data: result });
	} catch (error) {
	self.postMessage({ success: false, error: error.message });
	}
	};
2. Blazor Wasm组件调用Web Worker
razor 
	<!-- Blazor组件Pages/Upload.razor -->
	@page "/upload"
	@inject IJSRuntime JsRuntime
	
	<h1>大文件上传示例</h1>
	
	<input type="file" @onchange="HandleFileChange" />
	<button @onclick="UploadFile" disabled="@(file == null || isUploading)">上传</button>
	
	@if (isUploading)
	{
	<p>上传中...</p>
	}
	@if (uploadResult != null)
	{
	<p>@uploadResult</p>
	}
	
	@code {
	private IJSObjectReference worker;
	private IBrowserFile file;
	private bool isUploading;
	private string uploadResult;
	
	protected override async Task OnInitializedAsync()
	{
	// 1. 创建Web Worker
	worker = await JsRuntime.InvokeAsync<IJSObjectReference>(
	"new Worker", "./js/uploadWorker.js");
	// 2. 监听Web Worker的消息
	await worker.InvokeVoidAsync("addEventListener", "message", DotNet.CreateJSCallback((object msg) =>
	{
	var result = msg as Dictionary<string, object>;
	isUploading = false;
	if ((bool)result["success"])
	{
	uploadResult = "上传成功!";
	}
	else
	{
	uploadResult = $"上传失败:{result["error"]}";
	}
	StateHasChanged();
	}));
	}
	
	private void HandleFileChange(InputFileChangeEventArgs e)
	{
	file = e.File;
	}
	
	private async Task UploadFile()
	{
	if (file == null) return;
	isUploading = true;
	uploadResult = null;
	// 3. 把文件发送给Web Worker
	await worker.InvokeVoidAsync("postMessage", file);
	}
	
	// 4. 释放Web Worker
	public async ValueTask DisposeAsync()
	{
	await worker.InvokeVoidAsync("terminate");
	await worker.DisposeAsync();
	}
	}

代码逐行拆解(结合Web Worker原理)

  1. Web Worker的作用
    后台线程执行:Web Worker在后台线程执行,不会阻塞主线程(UI线程),适合处理大文件上传、下载等耗时操作;
    沙箱限制:Web Worker不能直接访问DOM,只能通过消息和主线程通信;
    生产级技巧:用Web Worker处理网络请求,提升UI流畅度,避免用户操作时卡顿。
  2. 用WebRTC实现P2P通信:浏览器之间直接传数据
    大白话解释WebRTC:浏览器之间直接建立连接,不需要经过服务器(除了信令服务器),适合做视频通话、文件共享等场景;
    Blazor Wasm通过JS互操作调用WebRTC API,实现P2P通信(代码示例略,核心是通过JS互操作调用浏览器的RTCPeerConnection API)。
    六、生产级Blazor Wasm网络优化技巧
  3. Wasm文件压缩:减少加载时间
    用Blazor Wasm的压缩功能:在Program.cs中添加builder.WebHost.UseStaticWebAssets();,然后在csproj文件中设置true
    用Gzip/Brotli压缩:在服务器上配置Gzip或Brotli压缩,比如Nginx配置gzip on;,可以把Wasm文件压缩50%以上;
    生产级技巧:用dotnet publish -c Release发布,会自动压缩Wasm文件。
  4. 性能监控:用浏览器DevTools监控网络请求
    Network面板:查看HTTP请求的时间、大小、缓存情况;
    Performance面板:查看UI线程的阻塞情况,优化Web Worker的使用;
    Blazor Wasm性能监控:用BlazorProfiler监控组件的渲染时间,优化UI性能。
  5. 安全优化:避免XSS攻击
    输入验证:对用户输入的内容进行验证,避免注入恶意脚本;
    CSP(内容安全策略):在index.html中添加CSP头,比如,限制只能加载同源的资源;
    生产级技巧:用Microsoft.AspNetCore.Components.WebAssembly.Authentication包实现身份验证,避免手动处理JWT令牌导致的安全问题。
    七、总结与选型建议
  6. Blazor Wasm网络编程选型表
场景 推荐技术
普通Web请求 Blazor Wasm HttpClient + 后端CORS配置
实时通信(聊天、通知) Blazor Wasm ClientWebSocket + 后端WebSocket服务
大文件上传/下载 JS Web Worker + Blazor Wasm JS互操作
P2P通信(视频通话、文件共享) WebRTC + Blazor Wasm JS互操作
  1. Blazor Wasm网络编程流程
    1.前端开发:用Blazor Wasm的HttpClient或ClientWebSocket实现网络请求,注意跨域配置;
    2.后端配置:配置CORS、WebSocket服务,支持前端的网络请求;
    3.性能优化:用Web Worker处理耗时操作,压缩Wasm文件,添加缓存策略;
    4.安全优化:配置CSP、输入验证,实现身份验证;
    5.测试部署:在浏览器中测试,部署到CDN或云服务器,提升加载速度。
    现在你已经掌握了Blazor Wasm网络编程的核心用法,从HTTP请求到WebSocket通信,从JS互操作到Web Worker优化,以后WebAssembly网络开发不用慌,按照这个流程来,90%的问题都能提前避免!下一节我们会学习“Wasm与.NET互操作的高级技巧”,结合Wasm的内存模型,教你实现更高效的网络通信!

转载请注明出处:https://www.xin3721.com/ArticlecSharp/c49598.html


相关教程