目录
介绍
背景
解决方案的说明
A C#类来控制Chrome浏览器
将Chrome与.Net Core控制台应用程序(静态Web应用程序)一起使用
将Chrome与AspNet Core MVC应用程序结合使用
Chrome类的Launch()方法
“普通” HTML演示应用程序
AspNet Core演示应用程序
发布AspNet Core演示应用程序
结论
下载最新的存储库存档
一种使用C#和Chrome开发跨平台桌面GUI应用程序的方法,它是Electron和库的非常轻巧的替代方案。
源代码可以在github上找到
介绍
该项目使用现有的Chrome安装程序将AspNet Core应用程序或由静态文件(html,javascript和css)组成的纯HTML应用程序呈现为桌面应用程序。
不需要Chromium或NodeJS,也不需要Chromium嵌入式框架(CEF)或CefSharp。
背景
这个想法很古老:为什么不使用html,javascript和css构建可被浏览器执行从而跨平台的桌面应用程序?
这个问题有很多答案。其中一些如下:
ChromelyOouiApache Cordova(mobile)Ionic(mobile)SpiderEyeSteve Sanderson's WebWindowWebViewWebView-csGoogle's CarloCarloSharpPositron
解决方案的说明
此解决方案基于以下想法:在运行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 methodChrome.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 optionsIsAspNetCoreApp = 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 windowArgList.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 ChromeBrowser = await Puppeteer.LaunchAsync(LaunchOptions);// get the main tab pagePage[] Pages = await Browser.PagesAsync().ConfigureAwait(false);TabPage = Pages[0];// event handler for static filesif (!IsAspNetCoreApp){await TabPage.SetRequestInterceptionAsync(true);TabPage.Request += StaticRequestHandler;await TabPage.GoToAsync(Chrome.HomeUrl, WaitUntilNavigation.DOMContentLoaded);}// event handler for closeTabPage.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 预览版模板生成的代码更多的代码。
为了使该应用程序正常工作,需要做一些事情。
项目文件的第一个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的安装位置。