目录
介绍
背景
解决方案的说明
A C#类来控制Chrome浏览器
将Chrome与.Net Core控制台应用程序(静态Web应用程序)一起使用
将Chrome与AspNet Core MVC应用程序结合使用
Chrome类的Launch()方法
“普通” HTML演示应用程序
AspNet Core演示应用程序
发布AspNet Core演示应用程序
结论
一种使用C#和Chrome开发跨平台桌面GUI应用程序的方法,它是Electron和Electron.NET库的非常轻巧的替代方案。
源代码可以在github上找到
该项目使用现有的Chrome安装程序将AspNet Core应用程序或由静态文件(html,javascript和css)组成的纯HTML应用程序呈现为桌面应用程序。
不需要Chromium或NodeJS,也不需要Chromium嵌入式框架(CEF)或CefSharp。
这个想法很古老:为什么不使用html,javascript和css构建可被浏览器执行从而跨平台的桌面应用程序?
这个问题有很多答案。其中一些如下:
此解决方案基于以下想法:在运行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浏览器。
该项目包含一个名为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应用程序。
这是在.Net Core控制台应用程序中使用它的方法,以便将纯HTML应用程序呈现为桌面应用程序。
static void Main(string[] args)
{
ManualResetEvent CloseEvent = new ManualResetEvent(false);
Chrome.Launch(new ChromeStartOptions(false), () => {
CloseEvent.Set();
});
CloseEvent.WaitOne();
}
这里是如何从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();
});
}
通过ChromeStartOptions实例传递给Launch()方法的标志指示应用程序的类型。True表示AspNet Core,而false表示纯静态HTML应用程序。
调用者可以通过ChromeStartOptions类的实例将更多信息传递给Launch()方法。ChromePath就是这样一点信息,表示可以找到Chrome的路径。
目前,该Crome类已尽力寻找在Windows和Linux上安装Chrome浏览器的位置。我计划研究Chrome启动器项目的代码,并尝试更好地解决此问题。
该Chrome.Launch()方法调用Chrome.LaunchAsync()执行以下操作的方法:
这是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 ArgList = new List(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;
};
}
}
简单的情况。这是一个.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 3.0 MVC应用程序,没有比Visual Studio 2019预览版模板生成的代码更多的代码。
为了使该应用程序正常工作,需要做一些事情。
项目文件的第一个PropertyGroup应如下所示。
netcoreapp3.0
OutOfProcess
WinExe
上面的代码使应用程序处于“进程外”状态,并在运行时隐藏了控制台。
随后是在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();
});
}
与模板代码的唯一区别在于,它配置了Kestrel以侦听所选端口。
这是正在运行的应用程序。
这是发布设置。
在“修剪”所有未使用的程序集之后,以上内容在单个文件中创建了一个自包含部署。
这是发布文件夹的内容。* .exe大小为43 MB,包含所有内容,包括AspNet Core。只有静态文件位于wwwroot文件夹中。
最棒的是:双击* .exe在Chrome浏览器中运行该应用程序。
多亏了Chrome和Puppeteer-Sharp,创建了使用Web技术构建的跨平台桌面应用程序的另一种可能性。这不需要Chromium或NodeJS或其他任何东西。它唯一需要知道的是Chrome的安装位置。