用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. Python 文件路径设置

    菜鸟教程:https://www.runoob.com/python/os-chdir.html Python官方文件教程:https://docs.python.org/3.9/library/os ...

  2. Markdown 编写技巧汇总(一)

    编写文档,有很多格式选择,也有不同平台选择.下面就自己接触到的MarkDown编写文档的各种技巧做简单梳理,供自己参阅,也希望帮到网友. [1]添加空格 ①   这种写法比较老土,但是,很实用!注意都 ...

  3. 运用shapefile.js解析Shp文件

    <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8& ...

  4. iOS自定义拍照框拍照&裁剪(一)

    卡片机时代 很重要的一点是,相机本身是没有方向概念的,它不理解拍摄的内容,只会以相机自己的坐标系去保存数据,下图展示了相机对"F"进行四个角度拍摄时返回的图片数据. 最初的卡片机时 ...

  5. 每日总结:Java课堂测试第三阶段第二次优化 (四则运算) (2021.9.22)

    package jisuan2; import java.util.*;import java.util.Scanner; public class xiaoxue { public static v ...

  6. Java语言程序设计与数据结构(基础篇)第七章答案

    答案为本人求解,如有错误,还望海涵.如有雷同,纯属巧合. 7.1 import java.util.Scanner; public class Main { public static void ma ...

  7. 题解 「BZOJ3636」教义问答手册

    题目传送门 Description 作为泉岭精神的缔造者.信奉者.捍卫者.传承者,Pear决定印制一些教义问答手册,以满足泉岭精神日益增多的信徒.Pear收集了一些有关的诗选.语录,其中部分内容摘录在 ...

  8. 10-1 Python 学习笔记

    1. 项目 在文本编辑器中新建一个文件,写几句话来总结一下你至此学到的 Python 知识,其中每一行都以"In Python you can"打头. 将这个文件命名为learni ...

  9. Java 读取PDF中的表格

    一.概述 本文以Java示例展示读取PDF中的表格的方法.这里导入Spire.PDF for Javah中的jar包,并使用其提供的相关及方法来实现获取表格中的文本内容.下表中整理了本次代码使用到的主 ...

  10. 【Java虚拟机6】Java内存模型(Java篇)

    什么是Java内存模型 <Java虚拟机规范>中曾试图定义一种"Java内存模型"(Java Memory Model,JMM)来屏蔽各种硬件和操作系统的内存访问差异, ...