65.9K
CodeProject 正在变化。 阅读更多。
Home

增强型 C# IHostBuilder/IHost 和依赖注入解析器库

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2023年1月2日

GPL3

5分钟阅读

viewsIcon

21823

一种基于 Autofac 的链式/声明式方法来创建 IHostBuilder/IHost 和通用的依赖注入解析器

约束

有人曾将代码框架描述为自愿穿上的束缚衣。我在这里描述的扩展的 IHostViewModelLocator API 要求使用 Autofac 作为其依赖注入组件。它还使用我编写的其他几个库,例如 J4JLogging,它以各种方式扩展了 Serilog。我在这里使用的所有库都是开源的。

如果您有兴趣将这些系统改编到其他库,我鼓励您 fork GitHub 存储库并进行尝试。并告诉我您做了什么!

引言

创建 IHost 实例的基本方法,您可以使用它作为大多数 Windows 应用程序(可能还有其他应用程序)的框架,涉及创建 HostBuilder 实例、配置它,然后构建它。如果一切顺利,您将获得一个 IHost 实例,其中包含一个 Services 属性,您可以使用它来动态检索服务。

我认为配置 HostBuilder 相当令人困惑。特别是对于我一直使用的某些功能,例如日志记录、数据保护(即加密/解密)以及配置命令行参数的解析方式。您可以完成所有必需的操作……但它并不特别直观。在使用 IHost 实例时,还有一些我希望引用的属性,但这些属性由于与其他我编写的支持库相关而难以访问或不可用。

我扩展了 IHost 接口和 IHostBuilder 系统来满足这些额外需求。派生接口是 IJ4JHost

public interface IJ4JHost : IHost
{
    string Publisher { get; }
    string ApplicationName { get; }

    string UserConfigurationFolder { get; }
    List<string> UserConfigurationFiles { get; }
    string ApplicationConfigurationFolder { get; }
    List<string> ApplicationConfigurationFiles { get; }

    bool FileSystemIsCaseSensitive { get; }
    StringComparison CommandLineTextComparison { get; }
    ILexicalElements? CommandLineLexicalElements { get; }
    CommandLineSource? CommandLineSource { get; }
    OptionCollection? Options { get; }

    OperatingSystem OperatingSystem { get; }
    AppEnvironment AppEnvironment { get; }
}

您可以通过在配置了 J4JHostConfiguration 实例后调用其 Build() 方法来创建 IJ4JHost 的实例。

通过调用各种扩展方法来配置 J4JHostConfiguration 实例。有两个必选方法您必须调用:Publisher(),用于定义应用程序发布者的名称,以及 ApplicationName(),用于定义应用程序的名称。这两个值对于解析配置文件路径和内置的加密/解密支持都很重要。

您可以在 GitHub 文档 中阅读所有可选扩展方法的详细信息。

集中式依赖注入解析器的论证

IJ4JHost 本身就很有用。但我发现将其与集中式依赖注入解析器耦合更有用。原因如下。

虽然 IJ4JHost 系统允许您创建一个具有各种有用功能的 IHost 应用程序控制器(例如,日志记录、命令行处理),但它本身在简单的控制台应用程序中比在 Windows 桌面应用程序中更有用。原因在于它没有内置的依赖注入支持。

在简单的控制台应用程序中,通常一次只运行一个“东西”。集成依赖注入相对简单,因为通常您需要处理的唯一特殊的动态创建对象的案例是获取第一个单例应用程序控制器。一旦完成,大多数控制台应用程序架构会根据创建它们的代码中的本地信息,按需创建它们需要的对象。至少在我简单的控制台应用程序中是这样;您的使用情况可能有所不同。

Windows 桌面应用程序则大不相同。在创建单个根应用程序控制器并按需在本地创建所有内容方面,如果可能的话,非常困难。这与代码及其所需对象被激活的多重方式有关。

AspNetCore 应用程序中也会发生类似的事情。但 AspNetCore 内置了对依赖注入的支持。您可以使用需要动态创建的构造函数参数来定义您的对象,并且只要您已将参数类型注册到依赖注入框架,就可以确保一切正常。

我不知道有任何 Windows 桌面架构包含同类型的内置依赖注入支持。Windows Forms 没有。WPF 没有。Windows App v2 没有。Windows App v3 没有,尽管我认为这是计划添加的功能。UWP 可能有;我从未在 UWP 中做过任何工作。

结果是,要在大多数或所有 Windows 桌面架构中使用依赖注入,您需要使用某种 ViewModelLocator 模式:一个带有 static 方法的类,可以按需调用以创建已注册到依赖注入系统的对象。

我编写了 J4JDeusEx 来实现一个通用的 ViewModelLocator 对象,该对象与我的 IJ4JHost API 集成,因此无论我是在编写控制台应用程序还是 Windows 桌面应用程序,我都可以以统一的方式与依赖注入进行交互。它的接口全是 static 的,而且很简单

public class J4JDeusEx
{
    public static IServiceProvider ServiceProvider { get; protected set; }
    public static bool IsInitialized { get; protected set; }
    public static string? CrashFilePath { get; protected set; }
    public static IJ4JLogger? Logger { get; protected set; }
    public static void OutputFatalMessage( string msg, IJ4JLogger? logger );
}

您可以在其 GitHub 文档 中详细了解 J4JDeusEx 的架构和功能。

Using the Code

如果您不想使用 J4JDeusEx,构建 IJ4JHost 实例非常简单

var hostConfig = new J4JHostConfiguraton();

// configuration steps omitted; check the GitHub documentation for details

var host = hostConfig.Build();

但是,建议在调用 Build() 之前检查以确保 J4JHostConfiguration 对象已正确配置

var hostConfig = new J4JHostConfiguraton();

// configuration steps omitted; check the GitHub documentation for details

IJ4JHost? host = null;

if( hostConfig.MissingRequirements == J4JHostRequirements.AllMet )
    host = hostConfig.Build();
else
{
    // take remedial action, abort startup, etc.
}

如果您想利用 J4JDeusEx 的功能,过程会更复杂一些。您需要创建一个 J4JDeusExHosted(用于非沙盒环境)或 J4JDeusExWinApp(用于沙盒环境)的实例,并实现一个单独的 abstract protected 方法

protected override J4JHostConfiguration? GetHostConfiguration();

这是典型的派生类外观

// This class is marked as partial, but that's simply to keep the codebase clean
// check the GitHub documentation for details
internal partial class DeusEx : J4JDeusExHosted
{
    protected override J4JHostConfiguration? GetHostConfiguration()
    {
        var hostConfig = new J4JHostConfiguration( AppEnvironment.Console )
                        .ApplicationName( "WpFormsSurveyProcessor" )
                        .Publisher( "Jump for Joy Software" )
                        .LoggerInitializer( ConfigureLogging )
                        .AddDependencyInjectionInitializers
                                      ( ConfigureDependencyInjection )
                        .FilePathTrimmer( FilePathTrimmer );

        var cmdLineConfig = hostConfig.AddCommandLineProcessing
                                       ( CommandLineOperatingSystems.Windows )
                                      .OptionsInitializer( SetCommandLineConfiguration )
                                      .ConfigurationFileKeys
                                       ( true, false, "c", "config" );

        return hostConfig;
    }

    // remaining details omitted for clarity
}

最后一步是调用您的派生类的 Initialize() 方法。执行此操作的位置取决于您编写的是控制台应用程序还是 Windows 桌面应用程序。

控制台应用程序

internal class Program
{
    static void Main( string[] args )
    {
        var deusEx = new DeusEx();

        if( !deusEx.Initialize() )
        {
            J4JDeusEx.Logger?.Fatal("Could not initialize application");
            Environment.ExitCode = 1;
            return;
        }

        // launch application, usually via calling a service
    }

    // remaining details omitted for clarity
}

Windows 桌面应用程序

public partial class App : Application
{
    private readonly IJ4JLogger _logger;

    public App()
    {
        this.InitializeComponent();

        this.UnhandledException += App_UnhandledException;

        var deusEx = new GPSLocatorDeusEx();

        if ( !deusEx.Initialize() )
            throw new J4JDeusExException( "Couldn't configure J4JDeusEx object" );

        _logger = J4JDeusEx.ServiceProvider.GetRequiredService<IJ4JLogger>();
    }

    private void App_UnhandledException
    ( object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e )
    {
        J4JDeusEx.OutputFatalMessage
        ($"Unhandled exception: {e.GetType().Name}", null);
        J4JDeusEx.OutputFatalMessage( $"{e.Message}", null );
    }
}

Windows 桌面示例还展示了如何使用 J4JDeusEx 的崩溃文件组件。我们为未处理的异常实现了一个自定义处理程序,并使用 J4JDeusEx.OutputFatalMessage() 将异常信息写入崩溃文件。

初始化 J4JDeusEx 后,您可以在代码库中的任何位置将其用作 ViewModelLocator

var service = J4JDeusEx.ServiceProvider.Services.GetRequiredService<IFooBar>();

沙盒与非沙盒环境

沙盒环境是指应用程序对文件系统的访问不受限制。非沙盒环境是指应用程序可能可以访问文件系统的任何部分。

Windows Forms、WPF 和控制台应用程序是非沙盒环境的示例。

Windows Applications v3 和 UWP(以及 WinRT)是沙盒环境的示例。

关注点

IJ4JHostJ4JDeusEx 是从我先前编写的各个库演变而来的。就像许多代码编写一样,它们的开发源于我意识到我一遍又一遍地使用相同或相似的模式,并将它们抽象成一个通用框架。这就是为什么它们有时会经历一些根本性的重构的原因:)。

历史

  • v2.3.1:首次在 CodeProject 上发布
  • v2.3.3:扩展了应用程序配置文件定位方式
© . All rights reserved.