用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. Kubernetes全栈架构师(资源调度下)--学习笔记

    目录 StatefulSet扩容缩容 StatefulSet更新策略 StatefulSet灰度发布 StatefulSet级联删除和非级联删除 守护进程服务DaemonSet DaemonSet的使 ...

  2. 【k8s】使用k8s部署一个简单的nginx服务

    名词解释 Namespace 表示命名空间 Deployment 表示pod发布 Service 表示多个pod做为一组的集合对外通过服务的表示 kubectl 是k8s的命令行操作命令,可以创建和更 ...

  3. 2.1 附录--JVM指令手册

    栈和局部变量操作 将常量压入栈的指令 aconst_null 将null对象引用压入栈 iconst_m1 将int类型常量-1压入栈 iconst_0 将int类型常量0压入栈 iconst_1 将 ...

  4. 10.7 URI

    URI:   Uniform Resource Identifier  统一资源标识符 URL:  Uniform Resource Locator    统一资源定位符 URN: Uniform R ...

  5. 5-基本的sql查询以及函数的使用

    基本SQL查询语句以及函数的使用 格式元素 描述 YYYY 四位的年份 MONTH 月份的英文全称 MON 月份的英文简写 MM 月份的数字表示 DD 日起的1-31数字表示 D 星期几的数字表示1- ...

  6. Geostatistical Analyst Tools(Geostatistical Analyst 工具)

    Geostatistical Analyst 工具 1.使用地统计图层 # Process: GA 图层至格网 arcpy.GALayerToGrid_ga("", 输出表面栅格, ...

  7. MySQL8 根据某属性查询字段排名由自定义变量到rank()的变动

    在mysql8 之前的版本,因为没有rank()方法的存在,所以在对字段进行排名时,使用的是自定义自变量的方法,比如: select id,name,@rank=@rank+1 as ranks fr ...

  8. 在IDEA中创建SpringBoot项目01

    1.选择创建项目 2.填写项目信息 3. 4. 5.Finish后会下载,之后生成目录结构: 6.在自己的包目录结构下添加了Controllr和Entiy测试项目: Controller: 1 pac ...

  9. c++中的数学函数

    math.h 数学函数库,一些数学计算的公式的具体实现是放在math.h里,具体有:1 三角函数double sin (double);double cos (double);double tan ( ...

  10. 利用python爬取全国水雨情信息

    分析 我们没有找到接口,所以打算利用selenium来爬取. 代码 import datetime import pandas as pd from bs4 import BeautifulSoup ...