Pure DI 如何使用 Razor Pages 实现

How is Pure DI implemented with Razor Pages

这个问题类似于,但是这个问题是关于Razor Pages的,它需要一个不同的截取点。

我正在使用 Dependency Injection Principles, Practices, and Patterns (DIPP&P). Part of my application has a web API controller. To implement Pure DI with my controller, I was easily able to follow section 7.3.1 "Creating a custom controller activator" from DIPP&P to create a controller activator class, similar to the example found in DIPP&P 书中解释的纯 DI 方法制作一个 ASP.NET 核心应用程序。这是通过实施 IControllerActivator 并在 create 方法中组合我的组合根来完成的。

我的应用程序还将包含 Razor Pages。我想继续使用 Pure DI 方法,但我找不到任何有关如何执行此操作的示例。我的假设是我需要创建一个 RazorPageActivator class,它实现 ASP.NET 核心 GitHub 中的 IRazorPageActivator and add my composition root to the Activate method. However, after reviewing the RazorPageActivator class,它看起来非常复杂我担心如果我通过制作自己的 class 来实现 IRazorPageActivator 来拦截它(或覆盖它?),事情将会中断,我会一团糟。

我的问题是,如果可能的话,如何使用 Razor Pages 实现纯 DI?

使用 Razor Pages,IPageModelActivatorProvider 充当您的 Composition Root 的 Composer。这是一个基于默认 Visual Studio (2019) Razor Pages 项目模板的示例。

让我们从自定义 IPageModelActivatorProvider 开始,它作为您的 Composer,是您的 Composition Root 的一部分:

public class CommercePageModelActivatorProvider
    : IPageModelActivatorProvider, IDisposable
{
    // Singletons
    private readonly ILoggerFactory loggerFactory;

    public CommercePageModelActivatorProvider(ILoggerFactory loggerFactory) =>
        this.loggerFactory = loggerFactory;

    public Func<PageContext, object> CreateActivator(
        CompiledPageActionDescriptor desc) =>
        c => this.CreatePageModelType(c, desc.ModelTypeInfo.AsType());

    public Action<PageContext, object> CreateReleaser(
        CompiledPageActionDescriptor desc) =>
        (c, pm) => (pm as IDisposable)?.Dispose();

    private object CreatePageModelType(PageContext c, Type pageModelType)
    {
        // Create Scoped components
        var context = new CommerceContext().TrackDisposable(c);

        // Create Transient components
        switch (pageModelType.Name)
        {
            case nameof(IndexModel):
                return new IndexModel(this.Logger<IndexModel>(), context);
            case nameof(PrivacyModel):
                return new PrivacyModel(this.Logger<PrivacyModel>());
            default: throw new NotImplementedException(pageModelType.FullName);
        }
    }

    public void Dispose() { /* Release Singletons here, if needed */ }

    private ILogger<T> Logger<T>() => this.loggerFactory.CreateLogger<T>();
}

注意此实现的一些事项:

  • 此 class 的结构与 book's code samples 中给出的非常相似。
  • 它实现了 IDisposable 以允许处理它自己创建的单例。在这种情况下,在它的构造函数中没有创建单例,所以不需要处理任何东西。 ILoggerFactory 是“外部所有”;它由框架创建,并将由框架处理(如果需要)。
  • class 使用自定义 TrackDisposable 扩展方法(稍后显示),该方法允许跟踪作用域和瞬态依赖项。 TrackDisposable 方法会将这些实例添加到请求中,并允许框架在请求结束时处理它们。
  • 这就是 CreateReleaser 方法仅处置页面本身的原因。当您跟踪它们以进行处置时,所有其他创建的组件的处置由框架完成。您也可以选择跟踪页面本身;在这种情况下,您可以将 CreateReleaser 委托留空。
  • 有一个方便的 Logger<T>() 方法可以简化 ILogger<T> 实现的创建。这些来自框架并由 ILoggerFactory.
  • 创建

这是 TrackDisposable 扩展方法:

public static class DisposableExtensions
{
    public static T TrackDisposable<T>(this T instance, PageContext c)
        where T : IDisposable
    {
        c.HttpContext.Response.RegisterForDispose(instance);
        return instance;
    }
}

最后缺少的基础设施是将 CommercePageModelActivatorProvider 注册到框架的 DI 容器中。这是在 Startup class:

内部完成的
public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddRazorPages();

        // Register your custom component activator here
        services.AddSingleton<
            IPageModelActivatorProvider, CommercePageModelActivatorProvider>();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        ...
    }
}