ASP.NET Core Blazor 核心功能三:Blazor与JavaScript互操作——让Web开发更灵活

简介: 码农刚子带你深入Blazor与JavaScript互操作,掌握IJSRuntime调用、双向通信及集成Chart.js等实战技巧,提升Web开发灵活性。

嗨,大家好!我是码农刚子。今天我们来聊聊Blazor中C#与JavaScript互操作。我知道很多同学在听到"Blazor"和"JavaScript"要一起工作时会有点懵,但别担心,我会用最简单的方式带你掌握这个技能!

为什么要学JavaScript互操作?

想象一下:你正在用Blazor开发一个超棒的应用,但突然需要用到某个只有JavaScript才能实现的炫酷效果,或者要集成一个超好用的第三方JS库。这时候,JavaScript互操作就是你的救星!

简单来说,它让Blazor和JavaScript可以"握手合作",各展所长。下面我们就从最基础的部分开始。

1. IJSRuntime - 你的JavaScript通行证

在Blazor中,IJSRuntime是与JavaScript沟通的桥梁。获取它超级简单:

@inject IJSRuntime JSRuntime
<button @onclick="ShowAlert">点我弹窗!</button>
@code {
    private async Task ShowAlert()
    {
        await JSRuntime.InvokeVoidAsync("alert", "Hello from Blazor!");
    }
}

就两行关键代码:

  • @inject IJSRuntime JSRuntime - 拿到通行证
  • InvokeVoidAsync - 调用不返回值的JS函数

实际场景:比如用户完成某个操作后,你想显示一个提示,用这种方式就特别方便。

2. 调用JavaScript函数 - 不只是简单弹窗

当然,我们不会只满足于弹窗。来看看更实用的例子:

首先,在wwwroot/index.html中添加我们的JavaScript工具函数:

<script>
    // 创建命名空间避免全局污染
    window.myJsHelpers = {
        showNotification: function (message, type) {
            // 模拟显示一个漂亮的提示框
            const notification = document.createElement('div');
            notification.style.cssText = `
                position: fixed;
                top: 20px;
                right: 20px;
                padding: 15px 20px;
                border-radius: 5px;
                color: white;
                z-index: 1000;
                transition: all 0.3s ease;
            `;
            
            if (type === 'success') {
                notification.style.backgroundColor = '#28a745';
            } else if (type === 'error') {
                notification.style.backgroundColor = '#dc3545';
            } else {
                notification.style.backgroundColor = '#17a2b8';
            }
            
            notification.textContent = message;
            document.body.appendChild(notification);
            
            // 3秒后自动消失
            setTimeout(() => {
                notification.remove();
            }, 3000);
        },
        
        getBrowserInfo: function () {
            return {
                userAgent: navigator.userAgent,
                language: navigator.language,
                platform: navigator.platform
            };
        },
        
        // 带参数的计算函数
        calculateDiscount: function (originalPrice, discountPercent) {
            return originalPrice * (1 - discountPercent / 100);
        }
    };
</script>

然后在Blazor组件中使用:

@inject IJSRuntime JSRuntime
<div class="demo-container">
    <h3>JavaScript函数调用演示</h3>
    
    <button @onclick="ShowSuccessNotification" class="btn btn-success">
        显示成功提示
    </button>
    
    <button @onclick="ShowErrorNotification" class="btn btn-danger">
        显示错误提示
    </button>
    
    <button @onclick="GetBrowserInfo" class="btn btn-info">
        获取浏览器信息
    </button>
    
    <button @onclick="CalculatePrice" class="btn btn-warning">
        计算折扣价格
    </button>
    @if (!string.IsNullOrEmpty(browserInfo))
    {
        <div class="alert alert-info mt-3">
            <strong>浏览器信息:</strong> @browserInfo
        </div>
    }
    @if (discountResult > 0)
    {
        <div class="alert alert-success mt-3">
            <strong>折扣价格:</strong> ¥@discountResult
        </div>
    }
</div>
@code {
    private string browserInfo = "";
    private decimal discountResult;
    private async Task ShowSuccessNotification()
    {
        await JSRuntime.InvokeVoidAsync("myJsHelpers.showNotification", 
            "操作成功!数据已保存。", "success");
    }
    private async Task ShowErrorNotification()
    {
        await JSRuntime.InvokeVoidAsync("myJsHelpers.showNotification", 
            "出错了!请检查网络连接。", "error");
    }
    private async Task GetBrowserInfo()
    {
        var info = await JSRuntime.InvokeAsync<BrowserInfo>("myJsHelpers.getBrowserInfo");
        browserInfo = $"语言: {info.Language}, 平台: {info.Platform}";
    }
    private async Task CalculatePrice()
    {
        discountResult = await JSRuntime.InvokeAsync<decimal>(
            "myJsHelpers.calculateDiscount", 1000, 20); // 原价1000,8折
    }
    // 定义接收复杂对象的类
    private class BrowserInfo
    {
        public string UserAgent { get; set; }
        public string Language { get; set; }
        public string Platform { get; set; }
    }
}

Note

  • 使用InvokeVoidAsync调用不返回值的函数
  • 使用InvokeAsync<T>调用有返回值的函数,记得指定返回类型
  • 复杂对象会自动序列化/反序列化

3. 把.NET方法暴露给JavaScript - 双向操作

有时候,我们也需要让JavaScript能调用我们的C#方法。这就用到[JSInvokable]特性了。

@inject IJSRuntime JSRuntime
@implements IDisposable
<div class="demo-container">
    <h3>.NET方法暴露演示</h3>
    
    <div class="mb-3">
        <label>消息内容:</label>
        <input @bind="message" class="form-control" />
    </div>
    
    <div class="mb-3">
        <label>重复次数:</label>
        <input type="number" @bind="repeatCount" class="form-control" />
    </div>
    
    <button @onclick="RegisterDotNetMethods" class="btn btn-primary">
        注册.NET方法给JavaScript使用
    </button>
    
    <div id="js-output" class="mt-3 p-3 border rounded">
        <!-- JavaScript会在这里输出内容 -->
    </div>
</div>
@code {
    private string message = "Hello from .NET!";
    private int repeatCount = 3;
    private DotNetObjectReference<MyComponent> dotNetHelper;
    protected override void OnInitialized()
    {
        dotNetHelper = DotNetObjectReference.Create(this);
    }
    private async Task RegisterDotNetMethods()
    {
        await JSRuntime.InvokeVoidAsync("registerDotNetHelper", dotNetHelper);
    }
    [JSInvokable]
    public string GetFormattedMessage()
    {
        return string.Join(" ", Enumerable.Repeat(message, repeatCount));
    }
    [JSInvokable]
    public async Task<string> ProcessDataAsync(string input)
    {
        // 模拟一些异步处理
        await Task.Delay(500);
        return $"处理后的数据: {input.ToUpper()} (处理时间: {DateTime.Now:HH:mm:ss})";
    }
    [JSInvokable]
    public void ShowAlert(string alertMessage)
    {
        // 这个方法会被JavaScript调用
        // 在实际应用中,你可能会更新组件状态或触发其他操作
        Console.WriteLine($"收到JavaScript的警告: {alertMessage}");
    }
    public void Dispose()
    {
        dotNetHelper?.Dispose();
    }
}

对应的JavaScript代码:

// 在index.html中添加
function registerDotNetHelper(dotNetHelper) {
    // 存储.NET引用供后续使用
    window.dotNetHelper = dotNetHelper;
    
    // 演示调用.NET方法
    callDotNetMethods();
}
async function callDotNetMethods() {
    if (!window.dotNetHelper) {
        console.error('.NET helper 未注册');
        return;
    }
    
    try {
        // 调用无参数的.NET方法
        const message = await window.dotNetHelper.invokeMethodAsync('GetFormattedMessage');
        
        // 调用带参数的异步.NET方法
        const processed = await window.dotNetHelper.invokeMethodAsync('ProcessDataAsync', 'hello world');
        
        // 调用void方法
        window.dotNetHelper.invokeMethodAsync('ShowAlert', '这是从JS发来的消息!');
        
        // 在页面上显示结果
        const output = document.getElementById('js-output');
        output.innerHTML = `
            <strong>来自.NET的消息:</strong> ${message}<br>
            <strong>处理后的数据:</strong> ${processed}
        `;
        
    } catch (error) {
        console.error('调用.NET方法失败:', error);
    }
}

Note

  • 记得使用DotNetObjectReference来创建引用
  • 使用Dispose()及时清理资源
  • 异步方法要返回TaskTask<T>

4. 使用JavaScript库 - 集成第三方神器

这是最实用的部分!让我们看看如何集成流行的JavaScript库。

示例:集成Chart.js图表库

首先引入Chart.js:

<!-- 在index.html中 -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>

创建图表辅助函数:

// 在index.html中或单独的JS文件
window.chartHelpers = {
    createChart: function (canvasId, config) {
        const ctx = document.getElementById(canvasId).getContext('2d');
        return new Chart(ctx, config);
    },
    
    updateChart: function (chart, data) {
        chart.data = data;
        chart.update();
    },
    
    destroyChart: function (chart) {
        chart.destroy();
    }
};

Blazor组件:

@inject IJSRuntime JSRuntime
@implements IDisposable
<div class="chart-demo">
    <h3>销售数据图表</h3>
    <canvas id="salesChart" width="400" height="200"></canvas>
    
    <div class="mt-3">
        <button @onclick="LoadSalesData" class="btn btn-primary">加载销售数据</button>
        <button @onclick="SwitchToProfitChart" class="btn btn-secondary">切换到利润图表</button>
    </div>
</div>
@code {
    private IJSObjectReference chartInstance;
    private bool isSalesData = true;
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await InitializeChart();
        }
    }
    private async Task InitializeChart()
    {
        var config = new
        {
            type = "bar",
            data = new
            {
                labels = new[] { "一月", "二月", "三月", "四月", "五月" },
                datasets = new[]
                {
                    new
                    {
                        label = "销售额",
                        data = new[] { 65, 59, 80, 81, 56 },
                        backgroundColor = "rgba(54, 162, 235, 0.5)",
                        borderColor = "rgba(54, 162, 235, 1)",
                        borderWidth = 1
                    }
                }
            },
            options = new
            {
                responsive = true,
                plugins = new
                {
                    title = new
                    {
                        display = true,
                        text = "月度销售数据"
                    }
                }
            }
        };
        chartInstance = await JSRuntime.InvokeAsync<IJSObjectReference>(
            "chartHelpers.createChart", "salesChart", config);
    }
    private async Task LoadSalesData()
    {
        var newData = new
        {
            labels = new[] { "一月", "二月", "三月", "四月", "五月", "六月" },
            datasets = new[]
            {
                new
                {
                    label = "销售额",
                    data = new[] { 65, 59, 80, 81, 56, 75 },
                    backgroundColor = "rgba(54, 162, 235, 0.5)"
                }
            }
        };
        await JSRuntime.InvokeVoidAsync("chartHelpers.updateChart", 
            chartInstance, newData);
    }
    private async Task SwitchToProfitChart()
    {
        isSalesData = !isSalesData;
        
        var newData = isSalesData ? 
            new { 
                labels = new[] { "Q1", "Q2", "Q3", "Q4" },
                datasets = new[] {
                    new {
                        label = "销售额",
                        data = new[] { 100, 120, 110, 130 },
                        backgroundColor = "rgba(54, 162, 235, 0.5)"
                    }
                }
            } :
            new {
                labels = new[] { "Q1", "Q2", "Q3", "Q4" },
                datasets = new[] {
                    new {
                        label = "利润",
                        data = new[] { 30, 45, 35, 50 },
                        backgroundColor = "rgba(75, 192, 192, 0.5)"
                    }
                }
            };
        await JSRuntime.InvokeVoidAsync("chartHelpers.updateChart", 
            chartInstance, newData);
    }
    public async void Dispose()
    {
        if (chartInstance != null)
        {
            await JSRuntime.InvokeVoidAsync("chartHelpers.destroyChart", chartInstance);
        }
    }
}

常见问题与解决方案

问题1:JS互操作调用失败

症状:控制台报错,函数未定义

解决

try 
{
    await JSRuntime.InvokeVoidAsync("someFunction");
}
catch (JSException ex)
{
    Console.WriteLine($"JS调用失败: {ex.Message}");
    // 回退方案
    await JSRuntime.InvokeVoidAsync("console.warn", "功能不可用");
}

问题2:性能优化

对于频繁调用的JS函数,可以使用IJSInProcessRuntime

@inject IJSRuntime JSRuntime
@code {
    private IJSInProcessRuntime jsInProcess;
    protected override void OnInitialized()
    {
        jsInProcess = (IJSInProcessRuntime)JSRuntime;
    }
    private void HandleInput(ChangeEventArgs e)
    {
        // 同步调用,更高效
        jsInProcess.InvokeVoidAsync("handleInput", e.Value.ToString());
    }
}

问题3:组件销毁时资源清理

@implements IDisposable
@code {
    private DotNetObjectReference<MyComponent> dotNetRef;
    private IJSObjectReference jsModule;
    protected override async Task OnInitializedAsync()
    {
        dotNetRef = DotNetObjectReference.Create(this);
        jsModule = await JSRuntime.InvokeAsync<IJSObjectReference>(
            "import", "./js/myModule.js");
    }
    public async void Dispose()
    {
        dotNetRef?.Dispose();
        
        if (jsModule != null)
        {
            await jsModule.DisposeAsync();
        }
    }
}

Blazor的JavaScript互操作其实没那么难的。记住这几个关键点:

  • IJSRuntime 是你的通行证
  • InvokeVoidAsyncInvokeAsync 是主要工具
  • [JSInvokable] 让.NET方法对JS可见
  • 及时清理资源 很重要

现在你已经掌握了Blazor与JavaScript互操作的核心技能!试着在自己的项目中实践一下,示例源码更放在仓库:

https://github.com/shenchuanchao/BlazorApp/tree/master/BlazorAppWasm/Pages

以上就是《ASP.NET Core Blazor 核心功能二:Blazor与JavaScript互操作——让Web开发更灵活》的全部内容,希望你有所收获。关注、点赞,持续分享

相关文章
|
18天前
|
存储 SQL 搜索推荐
货拉拉用户画像基于 Apache Doris 的数据模型设计与实践
货拉拉基于Apache Doris构建高效用户画像系统,实现标签管理、人群圈选与行为分析的统一计算引擎,支持秒级响应与大规模数据导入,显著提升查询效率与系统稳定性,助力实时化、智能化运营升级。
130 14
货拉拉用户画像基于 Apache Doris 的数据模型设计与实践
|
5天前
|
监控 Kubernetes 安全
边界已死,信任重构:零信任架构的真相与落地心法
边界已死,信任重构:零信任架构的真相与落地心法
68 17
|
13天前
|
SQL 关系型数据库 MySQL
MySQL从入门到精通:系统性学习路径
“MySQL从入门到精通”系统梳理了从基础到高阶的完整学习路径,涵盖安装配置、SQL语法、数据库设计、事务锁机制、性能优化、主从复制及分库分表等核心内容,结合实战任务帮助开发者由浅入深掌握MySQL,助力成为数据库高手。
136 13
|
6天前
|
存储 缓存 数据挖掘
阿里云服务器租用价格,特价38元、99元、199元云服务器与最新活动价格参考
截止目前阿里云服务器价格最便宜主要有三款,轻量应用服务器2核2G峰值200M带宽38元1年;云服务器经济型e实例2核2G3M带宽99元1年;云服务器通用算力型u1实例2核4G5M带宽199元1年。除此之外,还有4核16G10M带宽只要89元/1个月、210元/3个月,8核32G10M带宽只要160元/1个月、480元/3个月。本文为大家分享目前阿里云的各个特价云服务器及活动价格情况,以供参考和选择。
165 17
|
24天前
|
存储 数据可视化 项目管理
Arya - 功能强大的在线 Markdown 编辑器
Arya(二丫)是一款基于Vue2与Vditor的开源在线Markdown编辑器,集流程图、甘特图、Echarts、PPT预览、五线谱等丰富功能于一体,支持多种编辑模式与一键导出PDF/图片,完美适配公众号等内容平台,3.3k+ GitHub stars,部署简单,体验优雅。
311 13
Arya - 功能强大的在线 Markdown 编辑器
|
6天前
|
人工智能 缓存 自然语言处理
构建AI智能体:三十九、中文新闻智能分类:K-Means聚类与Qwen主题生成的融合应用
K-Means作为最经典和广泛使用的聚类算法,以其简单性和效率在数据科学中占据重要地位。尽管有其局限性,但通过合理的初始化方法、参数调优和与大模型的结合,K-Means仍然能够解决许多实际聚类问题。与大型语言模型的结合代表了现代AI应用的一个重要方向,其中K-Means负责高效处理和大规模模式识别,而大模型负责深度的语义理解和内容生成,二者优势互补,构建出更加智能和高效的AI系统。
94 12
|
23天前
|
关系型数据库 MySQL 数据管理
MySQL数据库基本操作包括增加、删除、更新和查询
值得注意的是,虽然上述操作看起来直观易懂,但实际情况中可能会遇到数据类型、索引、性能优化和事务处理等高级话题。因此,数据库管理员或开发人员在对数据库进行操作时,应具备深入的理解和丰富的实践经验。
310 18
|
5天前
|
存储 运维 对象存储
日志别乱滚!从“日志即事件”到 Loki 的低成本集中化日志实战心法
日志别乱滚!从“日志即事件”到 Loki 的低成本集中化日志实战心法
70 14
|
20天前
|
网络协议 Linux 测试技术
Kali Linux 加入 Windows 域:完整的域渗透测试环境搭建指南
本文详述将Kali Linux加入Windows域的完整过程,涵盖DNS、Kerberos、SSSD配置及域枚举、渗透测试技术,打造专业内网渗透环境,适用于红队演练与安全研究。(238字)
197 10
|
11天前
|
人工智能 前端开发 安全
AI 最先替代的开发工作:从重复劳动到人机协同的新范式
AI正加速替代基础开发工作:CRUD页面、样板代码、简单Bug修复、文档生成与基础测试等重复性任务已可通过低代码平台与AI工具高效完成,显著提升生产力。据Gartner报告,70%企业内部系统已采用AI辅助开发,人力投入减少60%-80%。GitHub Copilot等工具更让开发者节省45%编码时间。然而,产品需求分析、系统架构设计、复杂交互体验及创新研发等需深度判断与创造力的工作,仍依赖人类智慧。未来开发者将转型为“AI指挥官”,聚焦问题定义、提示工程与人机协同,核心竞争力转向系统思维、业务理解与技术创新。
160 15