用blazor(Wasm)开发了一个chrome插件感觉效率挺高的,分享给大家

先简单介绍下WebAssembly的原理:

“WebAssembly是一种用于基于堆栈的虚拟机的二进制指令格式”

image

如上图,浏览器在执行js时是会经历 Parser转成语法树->Compiler转成字节码->JIT即时字节码解释执行

因为WebAssembly 模块已经被编译成一种 JavaScript 字节码形式,现代支持 WebAssembly 的 JavaScript 引擎可以在其 JIT 组件中可以直接解释执行!

mono团队把开源跨平台.NET运行时Mono(也是unity3d的运行时)编译成了WebAssembly ,那么开发的.net程序就可以通过这个运行时在浏览器中加载net程序执行。

近日vs2022发布了,blazor的功能得到进一步提升,

  • 支持AOT将.NET代码直接编译为WebAssembly字节码
  • 支持NativeFileReference添加c语言和rust等原生依赖

进入正题

开发浏览器插件,常见的就是按照插件的这几块api来进行扩展

  • 右键菜单扩展
  • Backgroud(可以理解为每个插件都有一个后台一直运行的模块)
  • popup(浏览器右上角点击插件弹出的窗口模块)
  • contentScript(嵌入到你想要嵌入的网站内执行)
  • devtools(开发面板扩展模块)

首先基于这个大佬的模板搭建工程

https://github.com/mingyaulee/Blazor.BrowserExtension

基于模板的话会帮你引入哪些包

image

我也躺了很多坑,看看我给大佬提的issue,和大佬一起成长

这里我总结一套非常高效的方案给大家:

  1. Backgroud用csharp写
  2. popup,option等的html不要用balzor写,balzor加载html没有任何优势
  3. contentScript用js写,内嵌到网站的,如果是balzor的话会初始化的时候卡1~2s左右,这个会严重影响体验

js和csharp交互

这里把BackGround(csharp开发)作为插件后端 html和js作为插件的前端的方式

右键菜单扩展

在BackGround里面写,包括响应事件

//选中跳转菜单
await WebExtensions.ContextMenus.Create(new WebExtensions.Net.Menus.CreateProperties
{
    Title = "测试菜单",
    Contexts = new List<ContextType>
    {
        ContextType.Selection
    },
    //data是选中的内容包装对象
    Onclick = async (data, tab) => { await test(data).ConfigureAwait(false); }
}, EmptyAction);
//非选中跳转菜单
 await WebExtensions.ContextMenus.Create(new WebExtensions.Net.Menus.CreateProperties
{
    Title = "跳转百度",
    Onclick = async (d, tab) => { await OpenUrl("https://www.baidu.com").ConfigureAwait(false); }
}, EmptyAction);

contentScript/popup等

用js写,有2种方式来和Backgroud通讯

1. 事件一来一回的方式

contentScript中发送消息给BackGround


chrome.runtime.sendMessage("消息体", function () { });

chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
    //处理backgroup发来的消息
    
});

BackGround注册事件用来接收js发过来的消息


//注册事件接收js过来的消息
await WebExtensions.Runtime.OnMessage.AddListener(OnReceivedCommand); //处理事件
private bool OnReceivedCommand(object obj, MessageSender sender, Action action){
    
   Console.WriteLine("OnCommand:" + key + $",from TabId:{sender.Tab.Id}");
   
   //处理完成后发送事件给js那边
    await WebExtensions.Tabs.SendMessage(sender.Tab.Id.Value, "处理完成了", new SendMessageOptions());
}

2. 长连接方式

js端

var port = chrome.extension.connect({
    name: "test"
}); port.onMessage.addListener(function (msg) {
    console.log(msg);
}); $('#test').click(e => {
    port.postMessage('发消息');
});

csharp端

await WebExtensions.Runtime.OnConnect.AddListener(port =>
{
    Console.WriteLine(port.Name + "---》connection");     port.OnMessage.AddListener(new DelegateMethod(async (msg) =>
    {
        //处理消息
    })); });

目前这种方式有一个需要优化,就是无法在csharp端主动推送消息给js端 给大佬提了issue了,相信很快可以fix https://github.com/mingyaulee/WebExtensions.Net/issues/14

配置/存储相关

有两种方法:

1. chrome.storage.local

这里我封装了一个类专门操作

public class ChromLocalStorage
{
    private readonly IWebExtensionsApi _webExtensionsApi;
    private readonly IJSRuntime _jsRuntime;     public ChromLocalStorage(IWebExtensionsApi webExtensionsApi, IJSRuntime JsRuntime)
    {
        _webExtensionsApi = webExtensionsApi;
        _jsRuntime = JsRuntime;
    }     /// <summary>
    /// 调用chrom.storage.local set 把 key 和 value设置进去
    /// key返回
    /// </summary>
    /// <param name="value"></param>
    /// <param name="existKey"></param>
    /// <returns></returns>
    public async Task<string> localSet(string value,string existKey  = null)
    {
        var key = existKey ?? "key_" + DateTime.Now.ToString("yyyyMMddHHmmss");
        byte[] bytes = Encoding.UTF8.GetBytes(value);
        var encode = Convert.ToBase64String(bytes);
        var jss = "var " + key + " = {'" + key + "':'" + encode + "'}";
        await _jsRuntime.InvokeVoidAsync("eval", jss);
        object data2 = await _jsRuntime.InvokeAsync<object>("eval", key);
        await _jsRuntime.InvokeVoidAsync("chrome.storage.local.set", data2);
        Console.WriteLine($"call chrome.storage.local.set,key:{key},value:{value},base64Value:{encode}");
        return key;
    }     public async Task<string> localSet<T>(T value)
    {
        if (value is string s)
        {
            return await localSet(s,null);
        }         //转成jsonstring         var serialize = JsonSerializer.Serialize(value);
        return await localSet(serialize,null);
    }     public async Task<T> localGet<T>(string key)
    {
        var data = await localGet(key);
        T deserialize = JsonSerializer.Deserialize<T>(data);
        return deserialize;
    }     public async Task<string> localGet(string key,bool remove=true)
    {
        try
        {
            var local = await _webExtensionsApi.Storage.GetLocal();
            var getData = await local.Get(new StorageAreaGetKeys(key));
            var data = getData.ToString();
            if (string.IsNullOrEmpty(data))
            {
                return string.Empty;
            }             var value = data.Split(new string[] { ":\"" }, StringSplitOptions.None)[1]
                .Split(new string[] { "\"" }, StringSplitOptions.None)[0];             var str = Convert.FromBase64String(value);
            var bastStr = Encoding.UTF8.GetString(str);
            //Console.WriteLine($"call chrome.storage.local.get,key:{key},value:{bastStr},base64Value:{value}");
            if (remove) await local.Remove(new StorageAreaRemoveKeys(key));
            return bastStr;
        }
        catch (Exception e)
        {
            return "";
        }
        
    }     public async Task localRemove(string key)
    {
        var local = await _webExtensionsApi.Storage.GetLocal();
        await local.Remove(new StorageAreaRemoveKeys(key));
    }
}

2. 6.0推出的新技术:采用EFCore + Sqlite

需要用到native的库 https://github.com/SteveSandersonMS/BlazeOrbital/blob/main/BlazeOrbital/ManufacturingHub/Data/e_sqlite3.o

下载下来后放入工程中,然后引入

image

这里还有一个关键

https://github.com/SteveSandersonMS/BlazeOrbital/blob/main/BlazeOrbital/ManufacturingHub/wwwroot/dbstorage.js

下载这个js后放入工程中,这个js是将sqlite和本地的indexdb进行同步的

//EF的DbContext
public class ClientSideDbContext : DbContext
{
    //定义你要存储的表模型
    public DbSet<Part> Parts { get; set; } = default!;     public ClientSideDbContext(DbContextOptions<ClientSideDbContext> options)
        : base(options)
    {
    }     protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        //设置你的表的索引等
        modelBuilder.Entity<Part>().HasIndex(x => x.Id);
        modelBuilder.Entity<Part>().HasIndex(x => x.Name);
        modelBuilder.Entity<Part>().Property(x => x.Name).UseCollation("nocase");
    }
} //sqlite的初始化以及获取DBContext的方法封装
public class DataSynchronizer
{
    public const string SqliteDbFilename = "app.db";
    private readonly Task firstTimeSetupTask;     private readonly IDbContextFactory<ClientSideDbContext> dbContextFactory;     public DataSynchronizer(IJSRuntime js, IDbContextFactory<ClientSideDbContext> dbContextFactory)
    {
        this.dbContextFactory = dbContextFactory;
        firstTimeSetupTask = FirstTimeSetupAsync(js);
    }     public async Task<ClientSideDbContext> GetPreparedDbContextAsync()
    {
        await firstTimeSetupTask;
        return await dbContextFactory.CreateDbContextAsync();
    }     private async Task FirstTimeSetupAsync(IJSRuntime js)
    {
        //只加载一次 让sqlite和indexdb同步
        var module = await js.InvokeAsync<IJSObjectReference>("import", "./js/dbstorage.js");         if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("browser")))
        {
            await module.InvokeVoidAsync("synchronizeFileWithIndexedDb", SqliteDbFilename);
        }         using var db = await dbContextFactory.CreateDbContextAsync();
        await db.Database.EnsureCreatedAsync();
    } }

image

在Program.cs进行注册

那么你就可以在Backgroud里面注入并在初始化方法中拿到db上下文

[Inject] public DataSynchronizer DataSynchronizer { get; set; }

//db上下文
private ClientSideDbContext db; protected override async Task OnInitializedAsync()
{
    await base.OnInitializedAsync();
    db = await DataSynchronizer.GetPreparedDbContextAsync();
}

推荐用新的方式,EF写起来更爽更高效,拿到db上下文 就可以很简单的操作插件里面所有用到存储配置等!

这种方式比较适合了解.net生态的人,结合.net的一些库还可以实现很多好玩的功能

  • excel导出
  • 二维码生成
  • ajax拦截,转发等

关注公众号一起学习

blazor wasm开发chrome插件的更多相关文章

  1. 使用 Vuejs 开发 chrome 插件的注意事项

    使用 Vuejs 开发 chrome 插件 chrome 插件的开发其实并不难,web开发者可以使用 html, css, javascript 轻松的开发实用的 chrome 插件. 一个好的 ch ...

  2. 使用Vuejs 开发chrome 插件的注意事项

    chrome 插件的开发其实并不难,web开发者可以使用 html, css, javascript 轻松的开发实用的 chrome 插件. 一个好的 chrome 插件可以提高我们的开发效率,甚至方 ...

  3. 自己开发chrome插件生成二维码

    摘要: 最近在开发微信项目时,需要在微信调试,所以经常会在微信中输入本地服务地址,输入起来特别麻烦,所以自己就想了想微信中的扫一扫,然后开发了这款chrome插件,将当前url生成二维码,用微信扫一扫 ...

  4. 开发chrome插件(扩展)

    官方文档 https://developer.chrome.com/extensions/getstarted.html [干货]Chrome插件(扩展)开发全攻略 http://blog.haoji ...

  5. 替代Infinity绝佳的自主开发chrome插件

    最近闲来无事在好朋(da)友(shen)的帮助下开发一个chrome插件,目的是为了替换infinity主页插件, 当然在此也推荐一波infinity确实不错,界面和易用性都是非常好用的水准了. 主页 ...

  6. 开发Chrome插件,实现网站自动登录

    近期被一个事情困扰着,我们采购了一款软件,里面有一个数据大屏页,当登录过期后,数据就会保持原状,不再更新.和供应商反馈了很多次,都无法彻底解决数据显示的问题,没办法,自己周末在家研究,网站自动登录的事 ...

  7. 试着开发chrome插件

    我的第一个chrome插件,是app形式的 代码如下 创建一个文件: 1.manifest.json { "version": "1.0", "man ...

  8. 使用Python开发chrome插件

    本文由 伯乐在线 - xianhu 翻译,Daetalus 校稿.未经许可,禁止转载!英文出处:pythonspot.com.欢迎加入翻译小组. 谷歌Chrome插件是使用HTML.JavaScrip ...

  9. Web前端开发Chrome插件

    参考:http://www.cnblogs.com/sosoft/p/3490481.html 越来越多的前端开发人员喜欢在Chrome里开发调试代码,Chrome有许多优秀的插件可以帮助前端开发人员 ...

随机推荐

  1. YbtOJ#763-攻城略池【线段树合并】

    正题 题目链接:http://www.ybtoj.com.cn/problem/763 题目大意 给出\(n\)个点的一棵树,每个\(d_i=0\)的点每秒会产生一个士兵往根节点走,走到一个节点让一个 ...

  2. VmWare装Linux&Centos步骤

    昨晚一次偶然的机会进入飞哥的直播间,他正在将用虚拟机搭建Linux环境的步骤,自己之前也确实安装过一次,不过没什么系统性总结,过程中有些步骤还需百度查找.于是乎今天决定从零基础在过一遍流程,便是这篇博 ...

  3. windwos10安装mysql8.0.20详细图文教程

    windwos10安装mysql8.0.20详细图文教程 1.浏览器搜索mysql下载安装 地址:https://dev.mysql.com/downloads/mysql/ 2.登录或者不登录下载 ...

  4. 使用CEF(二)— 基于VS2019编写一个简单CEF样例

    使用CEF(二)- 基于VS2019编写一个简单CEF样例 在这一节中,本人将会在Windows下使用VS2019创建一个空白的C++Windows Desktop Application项目,逐步进 ...

  5. 深入思考软件工程,开启 DevOps 之旅

    20 世纪 60 年代,软件开始脱离硬件,逐渐成为一个独立产业.至今,软件开发过程从瀑布模型.CMM/CMMI,到 20 年前敏捷的诞生,再到今天 DevOps 的火热,一代代软件人在思考和探索,如何 ...

  6. AI 事件驱动场景 Serverless 实践

    作者 | 李鹏(元毅) 来源 | Serverless 公众号 一.事件驱动框架:Knative Eventing 事件驱动是指事件在持续事务管理过程中,进行决策的一种策略.可以通过调动可用资源执行相 ...

  7. 洛谷4248 AHOI2013差异 (后缀数组SA+单调栈)

    补博客! 首先我们观察题目中给的那个求\(ans\)的方法,其实前两项没什么用处,直接\(for\)一遍就求得了 for (int i=1;i<=n;i++) ans=ans+i*(n-1); ...

  8. docker中Jenkins启动无法安装插件,版本过低

    一.问题现象: 使用docker启动jenkins,在jenkins启动后却无法安装jenkins的插件,一直提示安装失败且从log看到提示信息显示为需要升级jenkins的版本 二.原因分析: 在使 ...

  9. 为代码编写稳定的单元测试 [Go]

    为代码编写稳定的单元测试 本文档配套代码仓库地址: https://github.com/liweiforeveryoung/curd_demo 配合 git checkout 出指定 commit ...

  10. NodeJs安装与环境配置

    https://blog.csdn.net/qq_43285335/article/details/90696126 https://www.cnblogs.com/liuqiyun/p/813390 ...