目錄
將Chrome與.Net Core控制檯應用程序(靜態Web應用程序)一起使用
將Chrome與AspNet Core MVC應用程序結合使用
一種使用C#和Chrome開發跨平臺桌面GUI應用程序的方法,它是Electron和Electron.NET庫的非常輕巧的替代方案。
源代碼可以在github上找到
介紹
該項目使用現有的Chrome安裝程序將AspNet Core應用程序或由靜態文件(html,javascript和css)組成的純HTML應用程序呈現爲桌面應用程序。
不需要Chromium或NodeJS,也不需要Chromium嵌入式框架(CEF)或CefSharp。
背景
這個想法很古老:爲什麼不使用html,javascript和css構建可被瀏覽器執行從而跨平臺的桌面應用程序?
這個問題有很多答案。其中一些如下:
- Electron
- Electron.NET
- Chromely
- Ooui
- Apache Cordova (mobile)
- Ionic (mobile)
- SpiderEye
- Steve Sanderson's WebWindow
- WebView
- WebView-cs
- Google's Carlo
- CarloSharp
- Positron
解決方案的說明
此解決方案基於以下想法:在運行MS Windows或Linux或MacOS的計算機中,有很大一部分安裝了Google的Chrome瀏覽器。同時,.Net Core和AspNet Core在所有這些OS上運行。
所需要的是一種首先創建Chrome瀏覽器實例,然後指示其導航到“主頁” URL的方法。
已經有一個使用NodeJS的方式做以上說的內容:是 Mathias Bynens的優秀 Puppeteer的NodeJS庫。Google提供了一個有關Puppeteer 的門戶,其中包含許多有價值的信息和示例。
再有是Puppeteer的C#端口,Darío Kondratiuk的 Puppeteer-Sharp。
這是來自github的Puppeteer的描述。
Puppeteer是一個Node庫,它提供了高級API來通過DevTools協議控制Chrome或Chromium 。Puppeteer 默認情況下headless運行,但可以配置爲運行完整(non-headless)的Chrome或Chromium。
此解決方案不是基於Headless Chrome瀏覽器的。相反,它使用的是普通的Chrome窗口,其中只有一個標籤頁,根本沒有地址欄。Puppeteer,當然還有Puppeteer-Sharp都可以通過這種方式運行Chrome瀏覽器。
A C#類來控制Chrome瀏覽器
該項目包含一個名爲Chrome的靜態類,其中包含不到400行代碼,該類用於啓動Chrome並導航到第一個HTML頁面。爲此,Chrome 類提供Launch()方法
static public void Launch(ChromeStartOptions Options, Action Closed = null)
當瀏覽器關閉時,它接受一個Options對象和一個回調來調用。這是ChromeStartOptions類。
public class ChromeStartOptions
{
public ChromeStartOptions(bool IsAspNetCoreApp = true)
{
this.IsAspNetCoreApp = IsAspNetCoreApp;
}
public bool IsAspNetCoreApp { get; set; } = true;
public string ChromePath { get; set; } = "";
public string HomeUrl { get; set; } = @"Index.html";
public string ContentFolder { get; set; } = "wwwroot";
public int Left { get; set; } = 300;
public int Top { get; set; } = 150;
public int Width { get; set; } = 1024;
public int Height { get; set; } = 768;
}
HomeUrl和ContentFolder特性用於靜態HTML應用程序,而不是ASPNET Core應用程序。
將Chrome與.Net Core控制檯應用程序(靜態Web應用程序)一起使用
這是在.Net Core控制檯應用程序中使用它的方法,以便將純HTML應用程序呈現爲桌面應用程序。
static void Main(string[] args)
{
ManualResetEvent CloseEvent = new ManualResetEvent(false);
Chrome.Launch(new ChromeStartOptions(false), () => {
CloseEvent.Set();
});
CloseEvent.WaitOne();
}
將Chrome與AspNet Core MVC應用程序結合使用
這裏是如何從AspNet Core Startup類的Configure()方法內部調用它的方法,以便將AspNet Core MVC應用程序呈現爲桌面應用程序。
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// code here ....
// call Chrome as the last thing in the Configure method
Chrome.Launch(new ChromeStartOptions(true), () => {
IHostApplicationLifetime LifeTime = app.ApplicationServices.GetService(typeof(IHostApplicationLifetime)) as IHostApplicationLifetime;
LifeTime.StopApplication();
});
}
Chrome類的Launch()方法
通過ChromeStartOptions實例傳遞給Launch()方法的標誌指示應用程序的類型。True表示AspNet Core,而false表示純靜態HTML應用程序。
調用者可以通過ChromeStartOptions類的實例將更多信息傳遞給Launch()方法。ChromePath就是這樣一點信息,表示可以找到Chrome的路徑。
目前,該Crome類已盡力尋找在Windows和Linux上安裝Chrome瀏覽器的位置。我計劃研究Chrome啓動器項目的代碼,並嘗試更好地解決此問題。
該Chrome.Launch()方法調用Chrome.LaunchAsync()執行以下操作的方法:
- 處理傳入的選項
- 準備一個Puppeteer-Sharp的LaunchOptions實例。
- 調用Puppeteer.LaunchAsync(options)方法並返回一個Browser實例。
- 如果這是AspNet Core應用程序,則該選項實例已準備好並且已經包含初始Url。否則,此步驟將推遲。
- 現在Chrome已啓動並運行,並顯示一個標籤Page。
- 該代碼獲得對此Page的引用。
- 如果這不是 AspNet Core應用程序,請將事件處理程序鏈接到該Page以滿足傳如請求,因爲沒有web服務器。緊接着調用Page.GoToAsync(url)傳遞應用程序的“主頁” URL。
- 在下一步中,在兩種情況下,都將另一個事件處理程序鏈接到Page,以處理瀏覽器的關閉。
這是Chrome.LaunchAsync()方法的完整代碼。
static public async Task LaunchAsync(ChromeStartOptions Options, Action Closed = null)
{
if (Browser == null)
{
// prepare options
IsAspNetCoreApp = Options.IsAspNetCoreApp;
if (!string.IsNullOrWhiteSpace(Options.ContentFolder))
{
ContentFolder = Path.GetFullPath(Options.ContentFolder);
}
HomeUrl = !IsAspNetCoreApp ? $@"http://{SStaticApp}/{Options.HomeUrl}" : $"http://localhost:{Port}";
List<string> ArgList = new List<string>(DefaultArgs);
string AppValue = !IsAspNetCoreApp ? "data:text/html, loading..." : Chrome.HomeUrl;
ArgList.Add($"--app={AppValue}"); // The --app= argument opens Chrome in app mode that is no fullscreen, no url bar, just the window
ArgList.Add($"--window-size={Options.Width},{Options.Height}");
ArgList.Add($"--window-position={Options.Left},{Options.Top}");
LaunchOptions LaunchOptions = new LaunchOptions
{
Devtools = false,
Headless = false,
Args = ArgList.ToArray(),
ExecutablePath = !string.IsNullOrWhiteSpace(Options.ChromePath) ? Options.ChromePath : FindChromPath(),
DefaultViewport = null
};
// launch Chrome
Browser = await Puppeteer.LaunchAsync(LaunchOptions);
// get the main tab page
Page[] Pages = await Browser.PagesAsync().ConfigureAwait(false);
TabPage = Pages[0];
// event handler for static files
if (!IsAspNetCoreApp)
{
await TabPage.SetRequestInterceptionAsync(true);
TabPage.Request += StaticRequestHandler;
await TabPage.GoToAsync(Chrome.HomeUrl, WaitUntilNavigation.DOMContentLoaded);
}
// event handler for close
TabPage.Close += (sender, ea) => {
Closed?.Invoke();
Closed = null;
TabPage = null;
if (!Browser.IsClosed)
Browser.CloseAsync();
Browser = null;
};
}
}
“普通” HTML演示應用程序
簡單的情況。這是一個.Net Core 3.0控制檯應用程序。
輸出類型設置爲Windows應用程序只是爲了在運行時隱藏控制檯框。
該應用程序碰巧包含一個名爲wwwroot的文件夾,這是該ChromeStartOptions類的ContentFolder屬性的默認值。
public string ContentFolder { get; set; } = "wwwroot";
該ContentFolder屬性指示放置靜態文件(html,js,css)的根目錄文件夾。
wwwroot文件夾中包含index.html,這又恰好是默認值文件ChromeStartOptions類中的HomeUrl屬性的默認值。
public string HomeUrl { get; set; } = @"Index.html";
這是正在運行的應用程序。
AspNet Core演示應用程序
它是一個AspNet Core 3.0 MVC應用程序,沒有比Visual Studio 2019預覽版模板生成的代碼更多的代碼。
爲了使該應用程序正常工作,需要做一些事情。
項目文件的第一個PropertyGroup應如下所示。
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<AspNetCoreHostingModel>OutOfProcess</AspNetCoreHostingModel>
<ApplicationIcon />
<OutputType>WinExe</OutputType>
<StartupObject />
</PropertyGroup>
上面的代碼使應用程序處於“進程外”狀態,並在運行時隱藏了控制檯。
隨後是在Properties文件夾中找到的lauchSettings.json文件。
{
"profiles": {
"PuppetMvc": {
"commandName": "Project",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:5000"
}
}
}
這就是所有文件內容。只需一個配置文件,完全沒有有關IIS Express的設置。該5000端口實際上未被應用程序使用。該Chrome類的發現和使用第一個自由端口。
Program類應該是如下這樣。
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureKestrel(o =>
{
o.Listen(IPAddress.Loopback, Chrome.Port);
})
.UseStartup<Startup>();
});
}
與模板代碼的唯一區別在於,它配置了Kestrel以偵聽所選端口。
這是正在運行的應用程序。
發佈AspNet Core演示應用程序
這是發佈設置。
在“修剪”所有未使用的程序集之後,以上內容在單個文件中創建了一個自包含部署。
這是發佈文件夾的內容。* .exe大小爲43 MB,包含所有內容,包括AspNet Core。只有靜態文件位於wwwroot文件夾中。
最棒的是:雙擊* .exe在Chrome瀏覽器中運行該應用程序。
結論
多虧了Chrome和Puppeteer-Sharp,創建了使用Web技術構建的跨平臺桌面應用程序的另一種可能性。這不需要Chromium或NodeJS或其他任何東西。它唯一需要知道的是Chrome的安裝位置。