DotNetCore 3.0 助力 WPF 开发

2021-06-09 14:05

阅读:413

标签:ddt   水平   injection   它的   情况   end   视图   uil   .net core   

DotNetCore Is AnyWhere.

前言

Visual Studio 2019 已经正式发布了,DotNetCore 3.0 的正式版也指日可待。在之前的版本中,作为一名基于微软生态的传统 WPF 程序员看着隔壁同学在开发 DotNetCore 网站时用着各种特性好生羡慕,想着巨硬啥时候能让客户端开发者也能尝尝甜头。

那么,现在是时候可以尝试一下了。

需要说明的一点的是,DotNetCore 3.0 虽然跨平台,但是基于此的 WPF 却是针对 Windows 特定平台的实现,并不能跨 Linux 和 MacOS 。

开发环境准备

要想开发 DotNetCore 版本的 WPF,首先需要确保我们的机器上已经安装了如下

  • Visual Studio 2019 下载地址

需要安装的组件如下图所示

技术图片

技术图片

  • DotNetCore 3.0 SDK 下载地址

直接默认安装即可。

全新的开发体验

在首次使用 VS2019 创建 DotNetCore 版本的 WPF 程序时,VS 可能会给你爆个如下图所示的错误:

技术图片

按照错误提示即可解决该问题,如下图所示

技术图片

接着选择 TOOLS -> Options,配置如下图所示

技术图片

Hello World

首先,我们可以通过 VS 创建一个基于 DotNetCore 的 项目模板,然后我们看一下与传统的 WPF 项目模板有什么区别。如下图所示,创建一个 WPF 项目

技术图片

创建完成后,尝试编译编译运行(注:第一次编译可能需要较长时间),如下图所示

技术图片

如果我们仔细看一下这个新版的项目模板,会发现与传统的项目模板相比,有好几处发生了改变:

  • **.csproj 的组织方式发生了改变,与传统的组织方式相比,内容精简的快没有了;
  • 项目默认会引用 Microsoft.NETCore.PlatformsMicrosoft.WindowsDesktop.App,这两个 Package 都是针对 WinFormWPF 的特定包
  • 项目属性中也有一些改动
  • 生成目录中也有改动,会生成一些以 json 结尾的文件

上述这些改动都是最直观的改动,但是这些改动貌似不痛不痒,并不能吸引传统 WPF 开发者投入使用。接触过 DotNetCore Web 方向的开发者已经对里面的 DI,HttpClientFactory,EFCore 等使用的炉火纯青,那我们能不能在 WPF 中也使用这些东西呢?答案是必须要能啊,所有我们还需要探索一下它的一些硬核功能。下面列举几个我目前知道的几个我觉得很炫酷的功能。

使用 DI 和 Service Provider

能够使用 DIService Provider,这是我觉得最值得说一下的,因为它的使用方式简直和在 DotNetCore Web 里面的一摸一样,值得一说。

首先,我们创建一个 DotNetCore 版本的 WPF 项目,然后引用如下包:

  • Microsoft.Extensions.DependencyInjection
  • Microsoft.Extensions.Options.ConfigurationExtensions
  • Microsoft.Extensions.Configuration.FileExtensions
  • Microsoft.Extensions.Configuration.Json

注:上述包都需要安装 预览版本的 3.0.0 版本的才行

然后,在我们的项目根目录下创建一个 appsettings.json 文件,文件内容如下所示:

{
  "AppSettings": {
    "StringSetting": "Value",
    "IntegerSetting": 42,
    "BooleanSetting": true
  }
}

然后将该文件的 Build Action 设置为 ContentCopy To Output Directiory 设置为 Copy if newer

接着,我们在项目根目录下创建一个 AppSettings.cs 文件,用于隐射上面的 appsettings.json 文件,示例代码如下所示:

public class AppSettings
{
    public string StringSetting { get; set; }

    public int IntegerSetting { get; set; }

    public bool BooleanSetting { get; set; }
}

然后,我们创建一个自定义的接口服务 ISampleService和对应实现 SampleService,示例代码如下所示:

public interface ISampleService
{
    string GetCurrentDate();
}

public class SampleService : ISampleService
{
    public string GetCurrentDate() => DateTime.Now.ToLongDateString();
}

然后,修改我们的 App.xaml 文件,删除掉默认添加的启动视图代码 StartupUri="MainWindow.xaml"

接着,修改我们的 App.xaml.cs 文件,示例代码如下所示:

public partial class App : Application
{
    public IServiceProvider ServiceProvider { get; private set; }

    public IConfiguration Configuration { get; private set; }

    protected override void OnStartup(StartupEventArgs e)
    {
        // 初始化配置建造器
        var builder = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);

        // 获取配置建造器创建的对象
        Configuration = builder.Build();

        //配置全局服务容器
        var serviceCollection = new ServiceCollection();
        ConfigureServices(serviceCollection);

        ServiceProvider = serviceCollection.BuildServiceProvider();

        var mainWindow = ServiceProvider.GetRequiredService();
        mainWindow.Show();
    }

    private void ConfigureServices(IServiceCollection services)
    {
        // 向全局容器中注册一个视图
        services.AddTransient(typeof(MainWindow));

        // 在全局容器中配置 AppSettings
        services.Configure(Configuration.GetSection(nameof(AppSettings)));

        // 在全局容器中注册自定义服务
        services.AddScoped();
    }
}

修改完毕后,我们可以尝试编译我们的项目,如果不出意外的啊,我们的程序会正常启动起来。这里就不做截图说明了。

看了上述代码,是不是觉得很有意思啊,这种全新的开发模式顿时把我们的代码水平提升了好几个档次。这种使用方式和在 AspDotNetCore 简直一摸一样。比如,我们在上面注册了 AppSettings 和一个基于 ISampleService 接口的实现 SampleService,那么我们就可以在 MainWindow 的构造函数中使用,比如,我们可以参考下面的示例代码:

    public partial class MainWindow : Window
    {
        private readonly ISampleService _sampleService;
        private readonly IOptions _settings;
        public MainWindow(ISampleService sampleService, IOptions settings)
        {
            InitializeComponent();

            _sampleService = sampleService;
            var val = _sampleService.GetCurrentDate();

            _settings = settings;
        }

        private void ButtonExit_Click(object sender, RoutedEventArgs e)
        {
            Application.Current.Shutdown();
        }
    }

然后,我们可以监视一下 **_settings** 的值,如下图所示:

技术图片

通过这个简单的例子,我们可以看到这种全新方式的依赖注入已经得到微软的大力支持,将基于 .NetCoreCS模式BS模式 开发方式进行了统一,学习曲线是不是又下降了很多啊。

使用 HttpClientFactory

众所周知,HttpClient 在实际的使用场景中还是存在一些弊端,在 DotNetCore 的 Web 端中,很多同学用了 HttpClientFactory 如鱼得水,减少了很多不必要的麻烦。现在,我们同样可以将这一利器在 WPF 中使用。

我们新建一个基于 DotNetCore 3.0 的 WPF 项目,然后引入如下包:

  • Microsoft.Extensions.DependencyInjection
  • Microsoft.Extensions.Http

然后,修改我们的 App.xaml 文件,删除掉默认添加的启动视图代码 StartupUri="MainWindow.xaml",并修改 App.xaml.cs 文件,示例代码如下所示:

public partial class App : Application
{
    public ServiceProvider ServiceProvider { get; private set; }

    protected override void OnStartup(StartupEventArgs e)
    {
        var serviceCollection = new ServiceCollection();
        ConfigureServices(serviceCollection);

        ServiceProvider = serviceCollection.BuildServiceProvider();

        var mainView = ServiceProvider.GetRequiredService();
        mainView.Show();

        base.OnStartup(e);
    }

    private void ConfigureServices(ServiceCollection services)
    {
        services.AddHttpClient();
        services.AddTransient(typeof(MainWindow));
    }
}

最后,修改我们的 MainWindow.xaml.cs 文件,示例代码如下所示:

public partial class MainWindow : Window
{
    private readonly IHttpClientFactory _httpClientFactory;
    public MainWindow(IHttpClientFactory httpClientFactory)
    {
        InitializeComponent();

        _httpClientFactory = httpClientFactory;
    }

    private async void ButtonExit_Click(object sender, RoutedEventArgs e)
    {
        var client = _httpClientFactory.CreateClient();
        var html = await client.GetStringAsync("http://www.baidu.com");
        //Application.Current.Shutdown();
    }
}

这就是关于 HttpClientFactory 的简单使用。

使用 EFCore

最后介绍的一大利器就是巨硬的 EFCore,这个东西也很溜,值得我们尝试使用。这里我使用 Sqlite 为例。

我们新建一个基于 DotNetCore 3.0 的 WPF 项目,然后引入如下包:

  • Microsoft.Extensions.DependencyInjection
  • Microsoft.Extensions.Configuration.FileExtensions
  • Microsoft.Extensions.Configuration.Json
  • Microsoft.EntityFrameworkCore.Sqlite

首先,我们参考上面提到的使用方式,在项目根目录下创建一个 appsettings.json,文件,修改内容如下所示:

{
  "ConnectionStrings": {
    "SqlConnection": "datasource = default.sqlite"
  }
}

然后将该文件的 Build Action 设置为 ContentCopy To Output Directiory 设置为 Copy if newer

接着,我们创建一个 DataContext 类,示例代码如下所示:

public class DataContext : DbContext
{
    public DataContext(DbContextOptions options) : base(options)
    {
        this.Database.Migrate();
    }
}

然后删除掉 App.xaml 中的 StartupUri="MainWindow.xaml",并修改 App.xaml.cs,示例代码如下所示:

public partial class App : Application
{
    public ServiceProvider ServiceProvider { get; private set; }
    public IConfigurationRoot Configuration { get; private set; }

    protected override void OnStartup(StartupEventArgs e)
    {
        var builder = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);

        Configuration = builder.Build();

        var serviceCollection = new ServiceCollection();
        ConfigurationServices(serviceCollection);

        ServiceProvider = serviceCollection.BuildServiceProvider();
        var mainView =  ServiceProvider.GetRequiredService();
        mainView.Show();

        base.OnStartup(e);
    }

    private void ConfigurationServices(ServiceCollection services)
    {
        services.AddTransient(typeof(MainWindow));

        services.AddDbContext(options=>options.UseSqlite(Configuration.GetConnectionString("SqlConnection")));
    }
}

然后我们修改 MainWindow.xaml.cs,示例代码如下所示:

public partial class MainWindow : Window
{
    private readonly DataContext _dataContext;

    public MainWindow(DataContext  dataContext)
    {
        InitializeComponent();

        _dataContext = dataContext;
    }

    private void ButtonExit_Click(object sender, RoutedEventArgs e)
    {
        Application.Current.Shutdown();
    }
}

使用方法依然很简单。

支持 UWP 相关控件 和 Windows10 API

传统的 WPF 客户端,如果使用基于 UWP 的相关控件,则可以通过使用 WindowsCommunityToolkit 控件库来使用 UWP 的相关控件,该控件库目前可能还不是很完善,但是微软已经在不断添加新功能了。

UWP 是未来发展的趋势,但是对于传统的 WPF,如果想像 UWP 那样也能使用功能更加强大的 API,只需要通过简单添加一些引用就可以实现。微软之前有发布过具体使用的文章,文末有给出链接。

发布方式

基于 DotNetCore 3.0 的 WPF 项目发布方式还是和传统的 WPF 项目发布方式有所差异。全新的发布方式是基于 DotNetCore 的风格来进行设计的。在 Publish 的选项卡中,我们可以看到如下配置

技术图片

我们可以依据具体情况,来选择合适的发布方式进行发布。当然,我们也可以借助 Desktop App Converter 工具,将我们的应用分发到 windows Store 上。

总结

通过上述几个简单的示例,我们可以看到传统的 WPF 已经被微软注入了新鲜的血液,并且在微软生态下的 C/S端B/S端 开发模式渐趋相同。大大减轻了学习曲线。能够让技术在最短的时间里变现,这也是我最看重的地方。

无论是过去还是现在,我都时不时地听身边的人说不应该过度依赖工具,但是我想说的是 技术服务于现实,而工具只是为了加速变现,如果一项技术再好再优秀,它却不能创造现实价值,服务生活,那么我宁愿放弃使用它。微软为开发者提供了 DotNetCore 的好技术,而 VisualStudio 系列工具作为生产力工具,只是为了提高生产效率。在效率为王的今天,谁赢得了时间,就赢得了一切。

最后,我不打算吹捧 DotNetCoreWPFVisualStudio,以免有人说我会误导萌新。还是那句话,实践出真知,感兴趣的话可以自己动手尝试一下。此外,目前 DotNetCore 3.0 还是处于预览阶段,所以可能会有一些坑。但是谁能保证自己第一次就能把事情做的完美呢?时间会证明一切。

相关参考

  • What‘s new in .NET Core 3.0 (Preview 2)
  • Around and About .NET World
  • Calling Windows 10 APIs From a Desktop Application

DotNetCore 3.0 助力 WPF 开发

标签:ddt   水平   injection   它的   情况   end   视图   uil   .net core   

原文地址:https://www.cnblogs.com/hippieZhou/p/10637348.html


评论


亲,登录后才可以留言!