一、当C#遇上JavaScript的那些事儿
咱们做Web开发的,经常遇到一个头疼的问题:前端用JavaScript,后端用C#,这俩语言怎么愉快地玩耍?Blazor WebAssembly给出了一套漂亮的解决方案。想象一下,你可以在C#里直接调用JavaScript函数,反过来JavaScript也能调用C#方法,就像邻居串门一样方便。
先看个简单例子(技术栈:.NET 6 + Blazor WebAssembly):
// 在C#中调用JavaScript的alert函数
[JSInvokable]
public static async Task ShowAlert(string message)
{
// 通过IJSRuntime调用JS函数
await JSRuntime.InvokeVoidAsync("alert", message);
}
// 对应的JavaScript代码
window.sayHello = function(name) {
// 这个函数可以被C#调用
return `Hello, ${name}!`;
};
看到没?三行代码就实现了跨语言调用。IJSRuntime是Blazor提供的桥梁,JSInvokable特性则让C#方法对JavaScript可见。
二、深度互操作的四种姿势
1. 基础调用:你呼我应
最基础的用法就是相互调用函数。比如要在C#里获取浏览器窗口大小:
public async Task GetWindowSize()
{
// 调用JS的getWindowSize函数
var size = await JSRuntime.InvokeAsync<WindowSize>("getWindowSize");
Console.WriteLine($"Width: {size.Width}, Height: {size.Height}");
}
// JavaScript部分
window.getWindowSize = () => {
return {
width: window.innerWidth,
height: window.innerHeight
};
};
2. 回调函数:你中有我
有时候我们需要传递回调函数,比如处理按钮点击:
// 注册点击回调
await JSRuntime.InvokeVoidAsync("registerClickHandler",
DotNetObjectReference.Create(this));
// C#回调方法
[JSInvokable]
public void HandleClick(int x, int y)
{
Console.WriteLine($"Clicked at ({x}, {y})");
}
// JavaScript事件处理
window.registerClickHandler = (dotNetHelper) => {
document.addEventListener('click', (e) => {
dotNetHelper.invokeMethodAsync('HandleClick', e.clientX, e.clientY);
});
};
3. 对象传递:礼尚往来
复杂对象也能在两者间传递,比如处理JSON数据:
public class UserData
{
public string Name { get; set; }
public int Age { get; set; }
}
// C#调用JS处理数据
var user = new UserData { Name = "张三", Age = 30 };
var processed = await JSRuntime.InvokeAsync<UserData>("processUserData", user);
// JavaScript处理函数
window.processUserData = (user) => {
user.age += 1; // 给年龄加1
return user;
};
4. 模块化调用:高端玩法
对于大型项目,推荐使用JS模块:
// 加载JS模块
var module = await JSRuntime.InvokeAsync<IJSObjectReference>(
"import", "./scripts/myModule.js");
// 调用模块方法
await module.InvokeVoidAsync("doSomething");
// myModule.js内容
export function doSomething() {
console.log('模块化操作');
}
三、那些年我们踩过的坑
1. 类型转换的暗礁
JavaScript的弱类型和C#的强类型经常打架。比如:
// C#期望得到int
var count = await JSRuntime.InvokeAsync<int>("getCount");
// 但JS可能返回字符串"123"
window.getCount = () => "123"; // 这里会报错!
解决方案是做好类型校验:
window.getCount = () => {
const count = localStorage.getItem('count');
return parseInt(count) || 0; // 确保返回数字
};
2. 内存泄漏的陷阱
DotNetObjectReference必须手动释放:
public class MyService : IDisposable
{
private DotNetObjectReference<MyService> _jsRef;
public MyService()
{
_jsRef = DotNetObjectReference.Create(this);
}
[JSInvokable]
public void Callback() { /*...*/ }
public void Dispose()
{
_jsRef?.Dispose(); // 重要!
}
}
3. 异步调用的时序问题
JS调用C#方法时要注意异步特性:
// 错误示范
dotNetHelper.invokeMethod('SyncMethod'); // 可能阻塞
// 正确做法
await dotNetHelper.invokeMethodAsync('AsyncMethod');
四、实战:打造一个文件预览组件
让我们用互操作技术实现一个图片预览功能:
// C#部分
public async Task PreviewImage(Stream imageStream)
{
// 将流转换为Base64
using var memoryStream = new MemoryStream();
await imageStream.CopyToAsync(memoryStream);
var base64 = Convert.ToBase64String(memoryStream.ToArray());
// 调用JS显示图片
await JSRuntime.InvokeVoidAsync("showImagePreview",
$"data:image/jpeg;base64,{base64}");
}
// JavaScript部分
window.showImagePreview = (base64Image) => {
const preview = document.getElementById('image-preview');
preview.src = base64Image;
preview.style.display = 'block';
};
这个例子展示了如何:
- 在C#中处理文件流
- 转换为JS能识别的Base64格式
- 通过互操作更新DOM
五、性能优化小贴士
- 批量调用:减少跨语言调用次数
// 不好的做法
await JSRuntime.InvokeVoidAsync("updateName", name);
await JSRuntime.InvokeVoidAsync("updateAge", age);
// 优化方案
await JSRuntime.InvokeVoidAsync("updateUser", name, age);
- 使用内存缓存:对于频繁调用的JS方法
private IJSObjectReference? _cachedModule;
public async Task DoWork()
{
_cachedModule ??= await JSRuntime.InvokeAsync<IJSObjectReference>(
"import", "./scripts/work.js");
await _cachedModule.InvokeVoidAsync("doWork");
}
- 避免大对象传递:超过1MB的数据考虑分块传输
六、应用场景大盘点
- 集成现有JS库:比如调用Chart.js绘图
- 浏览器API访问:地理位置、摄像头等
- DOM精细操作:JS更擅长处理动画效果
- Web Worker:用JS处理密集型任务
- 第三方服务:如支付SDK集成
七、技术选型的思考
优点:
- 一套C#代码走天下,减少上下文切换
- 强类型安全,编译时就能发现错误
- 可以复用现有的.NET生态
缺点:
- 首次加载较慢(WebAssembly特性)
- 调试体验不如纯前端流畅
- 某些浏览器API需要polyfill
注意事项:
- 生产环境记得开启压缩和缓存
- 考虑添加加载动画改善用户体验
- 对于复杂动画,还是交给CSS/JS处理更合适
- 注意浏览器兼容性问题
八、总结
Blazor的互操作就像给C#和JavaScript办了张结婚证,让它们能合法地在一起过日子。虽然偶尔会吵架(类型冲突),但只要掌握好沟通技巧(规范调用),就能组建幸福的家庭(稳定应用)。记住:没有银弹,根据场景选择最合适的交互方式才是王道。
评论