blazor wasm开发chrome插件
用blazor(Wasm)開發(fā)了一個chrome插件感覺效率挺高的,分享給大家
先簡單介紹下WebAssembly的原理:
“WebAssembly是一種用于基于堆棧的虛擬機(jī)的二進(jìn)制指令格式”
image如上圖,瀏覽器在執(zhí)行js時是會經(jīng)歷 Parser轉(zhuǎn)成語法樹->Compiler轉(zhuǎn)成字節(jié)碼->JIT即時字節(jié)碼解釋執(zhí)行
因?yàn)閃ebAssembly 模塊已經(jīng)被編譯成一種 JavaScript 字節(jié)碼形式,現(xiàn)代支持 WebAssembly 的 JavaScript 引擎可以在其 JIT 組件中可以直接解釋執(zhí)行!
mono團(tuán)隊(duì)把開源跨平臺.NET運(yùn)行時Mono(也是unity3d的運(yùn)行時)編譯成了WebAssembly ,那么開發(fā)的.net程序就可以通過這個運(yùn)行時在瀏覽器中加載net程序執(zhí)行。
近日vs2022發(fā)布了,blazor的功能得到進(jìn)一步提升,
支持AOT將.NET代碼直接編譯為WebAssembly字節(jié)碼
支持NativeFileReference添加c語言和rust等原生依賴(手動狗頭)
進(jìn)入正題
開發(fā)瀏覽器插件,常見的就是按照插件的這幾塊api來進(jìn)行擴(kuò)展
右鍵菜單擴(kuò)展
Backgroud(可以理解為每個插件都有一個后臺一直運(yùn)行的模塊)
popup(瀏覽器右上角點(diǎn)擊插件彈出的窗口模塊)
contentScript(嵌入到你想要嵌入的網(wǎng)站內(nèi)執(zhí)行)
devtools(開發(fā)面板擴(kuò)展模塊)
首先基于這個大佬的模板搭建工程
https://github.com/mingyaulee/Blazor.BrowserExtension
基于模板的話會幫你引入哪些包
image我也躺了很多坑,看看我給大佬提的issue,和大佬一起成長
這里我總結(jié)一套非常高效的方案給大家:
Backgroud用csharp寫
popup,option等的html不要用balzor寫,balzor加載html沒有任何優(yōu)勢
contentScript用js寫,內(nèi)嵌到網(wǎng)站的,如果是balzor的話會初始化的時候卡1~2s左右,這個會嚴(yán)重影響體驗(yàn)
js和csharp交互
這里把BackGround(csharp開發(fā))作為插件后端 html和js作為插件的前端的方式
右鍵菜單擴(kuò)展
在BackGround里面寫,包括響應(yīng)事件
//選中跳轉(zhuǎn)菜單 await?WebExtensions.ContextMenus.Create(new?WebExtensions.Net.Menus.CreateProperties {Title?=?"測試菜單",Contexts?=?new?List<ContextType>{ContextType.Selection},//data是選中的內(nèi)容包裝對象Onclick?=?async?(data,?tab)?=>?{?await?test(data).ConfigureAwait(false);?} },?EmptyAction);//非選中跳轉(zhuǎn)菜單await?WebExtensions.ContextMenus.Create(new?WebExtensions.Net.Menus.CreateProperties {Title?=?"跳轉(zhuǎn)百度",Onclick?=?async?(d,?tab)?=>?{?await?OpenUrl("https://www.baidu.com").ConfigureAwait(false);?} },?EmptyAction);contentScript/popup等
用js寫,有2種方式來和Backgroud通訊
1. 事件一來一回的方式
contentScript中發(fā)送消息給BackGround
chrome.runtime.sendMessage("消息體",?function?()?{?});chrome.runtime.onMessage.addListener(function?(request,?sender,?sendResponse)?{//處理backgroup發(fā)來的消息});BackGround注冊事件用來接收js發(fā)過來的消息
//注冊事件接收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}");//處理完成后發(fā)送事件給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('發(fā)消息'); });csharp端
await?WebExtensions.Runtime.OnConnect.AddListener(port?=> {Console.WriteLine(port.Name?+?"---》connection");port.OnMessage.AddListener(new?DelegateMethod(async?(msg)?=>{//處理消息}));});目前這種方式有一個需要優(yōu)化,就是無法在csharp端主動推送消息給js端 給大佬提了issue了,相信很快可以fix https://github.com/mingyaulee/WebExtensions.Net/issues/14
配置/存儲相關(guān)
有兩種方法:
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>///?調(diào)用chrom.storage.local?set?把?key?和?value設(shè)置進(jìn)去///?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);}//轉(zhuǎn)成jsonstringvar?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推出的新技術(shù):采用EFCore + Sqlite
需要用到native的庫 https://github.com/SteveSandersonMS/BlazeOrbital/blob/main/BlazeOrbital/ManufacturingHub/Data/e_sqlite3.o
下載下來后放入工程中,然后引入
image這里還有一個關(guān)鍵
https://github.com/SteveSandersonMS/BlazeOrbital/blob/main/BlazeOrbital/ManufacturingHub/wwwroot/dbstorage.js
下載這個js后放入工程中,這個js是將sqlite和本地的indexdb進(jìn)行同步的
//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);//設(shè)置你的表的索引等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進(jìn)行注冊
那么你就可以在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生態(tài)的人,結(jié)合.net的一些庫還可以實(shí)現(xiàn)很多好玩的功能
excel導(dǎo)出
二維碼生成
ajax攔截,轉(zhuǎn)發(fā)等
我是正東,開發(fā)chrome插件其實(shí)很簡單,這種方式對于我來說比較高效 哈哈
歡迎白嫖 順手點(diǎn)個贊吧!
總結(jié)
以上是生活随笔為你收集整理的blazor wasm开发chrome插件的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: .NET 6新特性试用 | 隐式usin
- 下一篇: 如何高效的将 DataReader 转成