用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. React-高阶函数_函数柯里化

    高阶函数_函数柯里化 高阶函数(定义) 如果一个函数符合下面两个规范,就是高阶函数: 如果A函数,接收的参数是一个函数,那么A就是一个高阶函数(比如数组方法arr.map()接收的就是一个处理item ...

  2. 踩坑系列《六》Spring增加事务处理遇到异常解决办法

    当在对数据进行增删改操作时,需要用到事务的处理,所以在业务层中加入@Transactional注解,但是在执行业务操作的前面遇到异常,因此对异常进行抛出,但是数据又诡异地成功保存到数据库了. 解决方法 ...

  3. torch的下载及安装

    Pytorch官网:https://pytorch.org/ 安装的时候如果选择在官网上下载安装的话下载会很慢,试过梯子也是很慢,此处提供一种快速安装的方法. 1.由于我是window系统,我选择了国 ...

  4. 洛谷 P1862 输油管道问题

    题意 题目链接:P1862 输油管道问题 不难看出每个油井的 \(x\) 坐标是没用的,所以问题转化为如下. 代数意义:给出 \(n\) 个数 \(y_1,y_2,\ldots,y_n\),找一个数 ...

  5. SAE助力南瓜电影7天内全面Severless

    作者:李刚(寻如),阿里云解决方案架构师 南瓜电影APP是国内领先的专注于影视精品化运营的垂直类视频产品,在移动互联网.IPTV.OTT等客户端,面向广大中产阶级精英群体,提供有异于院线及其他视频平台 ...

  6. 1. SSTI(模板注入)漏洞(入门篇)

    好久没更新博客了,现在主要在作源码审计相关工作,在工作中也遇到了各种语言导致的一些SSTI,今天就来大概说一下SSTI模板注入这个老生常谈的漏洞 前言 模板引擎 模板引擎(这里特指用于Web开发的模板 ...

  7. Java(12)方法的重载

    作者:季沐测试笔记 原文地址:https://www.cnblogs.com/testero/p/15201592.html 博客主页:https://www.cnblogs.com/testero ...

  8. K12教培从业者转型指南 换个赛道依然可以再创辉煌

    随着"双减"政策的落地,属于K12教培机构的时代逐渐拉上帷幕,面对机会不再的K12教培行业,约70万机构和近千万的从业人员面临转型问题.压力之下,留下或离开?对广大K12教培机构从 ...

  9. 【UE4 C++】DateTime、Timespan 相关函数

    基于UKismetMathLibrary DateTime 相关函数 Timespan 运算操作相关函数见尾部附录 /** Returns the date component of A */ UFU ...

  10. BUAA 软工 结对项目作业

    1.相关信息 Q A 这个作业属于哪个课程 2020春季计算机学院软件工程(罗杰 任健) 这个作业的要求在哪里 结对项目作业 我在这个课程的目标是 系统地学习软件工程开发知识,掌握相关流程和技术,提升 ...