使用AspNet Core或纯静态HTML文件的跨平台桌面应用程序

目录

介绍

背景

解决方案的说明

A  C#类来控制Chrome浏览器

将Chrome与.Net Core控制台应用程序(静态Web应用程序)一起使用

将Chrome与AspNet Core MVC应用程序结合使用

Chrome类的Launch()方法

“普通” HTML演示应用程序

AspNet Core演示应用程序

发布AspNet Core演示应用程序

结论


一种使用C#和Chrome开发跨平台桌面GUI应用程序的方法,它是ElectronElectron.NET库的非常轻巧的替代方案。

源代码可以在github上找到

介绍

该项目使用现有的Chrome安装程序将AspNet Core应用程序或由静态文件(htmljavascriptcss)组成的纯HTML应用程序呈现为桌面应用程序。

不需要ChromiumNodeJS,也不需要Chromium嵌入式框架(CEFCefSharp

背景

这个想法很古老:为什么不使用htmljavascriptcss构建可被浏览器执行从而跨平台的桌面应用程序?

这个问题有很多答案。其中一些如下:

解决方案的说明

此解决方案基于以下想法:在运行MS WindowsLinuxMacOS的计算机中,有很大一部分安装了GoogleChrome浏览器。同时,.Net CoreAspNet Core在所有这些OS上运行。

所需要的是一种首先创建Chrome浏览器实例,然后指示其导航到主页” URL的方法。

已经有一个使用NodeJS的方式做以上说的内容:是 Mathias Bynens优秀 PuppeteerNodeJS库。Google提供了一个有关Puppeteer 门户,其中包含许多有价值的信息和示例。

再有是PuppeteerC#端口Darío Kondratiuk Puppeteer-Sharp

这是来自githubPuppeteer的描述。

Puppeteer是一个Node库,它提供了高级API来通过DevTools协议控制ChromeChromium Puppeteer 默认情况下headless运行,但可以配置为运行完整(non-headless)的ChromeChromium

此解决方案不是基于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;
}

HomeUrlContentFolder特性用于静态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();
}

ChromeAspNet 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类已尽力寻找在WindowsLinux上安装Chrome浏览器的位置。我计划研究Chrome启动器项目的代码,并尝试更好地解决此问题。

Chrome.Launch()方法调用Chrome.LaunchAsync()执行以下操作的方法:

  • 处理传入的选项
  • 准备一个Puppeteer-SharpLaunchOptions实例。
  • 调用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属性指示放置静态文件(htmljscss)的根目录文件夹。

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文件夹中。

最棒的是:双击* .exeChrome浏览器中运行该应用程序。

结论

多亏了ChromePuppeteer-Sharp,创建了使用Web技术构建的跨平台桌面应用程序的另一种可能性。这不需要ChromiumNodeJS或其他任何东西。它唯一需要知道的是Chrome的安装位置。

发布了69 篇原创文章 · 获赞 139 · 访问量 45万+

猜你喜欢

转载自blog.csdn.net/mzl87/article/details/104262520