我做错了什么,DI或设计,我应该怎么做

本文关键字:我应该 DI 错了 什么 | 更新日期: 2023-09-27 17:56:17

长话短说,我当前正在编写的应用程序应该模拟当前登录的用户。

这是一个管理信息查询的应用程序。

由于NHibernate.ISessionFactory不允许在其连接字符串级别具有更大的灵活性,因此我需要使用当前用户凭据动态构建连接。(顺便说一句,我不是在抱怨NH,我在每个项目上使用它都很棒。

因此,我需要在启动时强制进行身份验证。许多依赖项可能在启动时绑定,但不是数据访问模块的依赖项,由于连接字符串,需要在用户进行身份验证后加载。

  1. 如果强制进行身份验证;
  2. AuthenticationPresenter告诉MembershipService使用提供的凭据对用户进行身份验证;
  3. 身份验证成功后,AuthetnicationUser类的实例将保留在MembershipService内,而又在单例范围内绑定;
  4. SessionFactoryProvider依赖于我的 MembershipService.CurrentUser 属性来检索用于生成连接字符串的凭据,因此 CurrentUser 属性在此模块的加载时不能为 null,因此我无法在应用程序启动时加载此模块而不会破坏某些内容。

它是一个 Windows 窗体应用程序,因此我使用 Program 类来实例化 Ninject IKernel并加载应用程序启动所需的模块。

应用程序稍后需要解析新的依赖项,例如数据访问,查询管理和依赖于ISession的存储库。

解决方案应该是能够加载需要在应用程序生命周期后期知道CurrentUser的模块,或者可能使用上下文或条件依赖项注入绑定。

数据模块

public class DataModule : NinjectModule {
    public override void Load() {
        Bind<ISessionFactory>().ToProvider<SessionFactoryProvider>().InSingletonScope();
        Bind<ISession>().ToProvider<SessionProvider>();
        Bind<IStatelessSession>().ToProvider<StatelessSessionProvider>();
    }
}

服务模块

public class ServiceModule : NinjectModule {
    public override void Load() {
        Bind<IMembershipService>().To<MembershipService>().InSingletonScope();
    }
}

身份验证模块

public class AuthenticationModule : NinjectModule {
    public override void Load() {
        Bind<AuthenticationPresenter>().ToSelf().InSingletonScope();
        Bind<IAuthenticationPresenterFactory>().ToFactory();
        Bind<IAuthenticationView>().To<AuthenticationForm>();
    }
}

查询管理模块

public class InquiriesManagementModule : NinjectModule {
    public override void Load() {
        Bind<IInquiriesManagementPresenterFactory>().ToFactory();
        Bind<InquiriesManagementPresenter>().ToSelf().InSingletonScope();
        Bind<IInquiriesManagementView>().To<InquiriesMgmtForm>();
        Bind<ICancelInquiryPresenterFactory>().ToFactory();
        Bind<CancelInquiryPresenter>().ToSelf();
        Bind<ICancelInquiryView>().To<CancelInquiryForm>();
        Bind<IEditInquiryPresenterFactory>().ToFactory();
        Bind<EditInquiryPresenter>().ToSelf();
        Bind<IEditInquiryView>().To<EditInquiryForm>();
        Bind<INewInquiryPresenterFactory>().ToFactory();
        Bind<NewInquiryPresenter>().ToSelf();
        Bind<INewInquiryView>().To<NewInquiryForm>();
        Bind<IInquiriesRepository>().To<InquiriesRepository>();
    }
}

如果您需要更多详细信息,请查看以下问题:

仅当属性不为 null 时才进行条件依赖项注入绑定

我还考虑编写一个 ApplicationContext 类,该类可能包含当前用户等基本信息,以将其传递给需要它的应用程序的表示者和提供程序,但问题仍然存在。

所以我想知道这是设计缺陷还是缺乏对依赖注入工具的了解,或者两者兼而有之。

编辑

在怀特黑德的评论@Simon,

您是否考虑过使用 WithConstructorArgument 方法传入用户凭据?

由于以下几个原因,我没有想到使用WithConstructorArgument

  1. 我有生以来第二次使用Ninject
  2. 确实认为我必须为这些参数提供值,我在应用程序启动时还没有这些值,因为还没有用户经过身份验证
  3. 依赖项是在应用程序启动之前加载的,因此它甚至在进入身份验证窗口之前就会抛出,这是用户在双击可执行文件时可以执行的第一件事,也是唯一的一件事

程序(在我决定去静态依赖注入工厂之前的情况)

[STAThread]
static void Main() {
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    IKernel dependencies = new StandardKernel(
        new ApplicationModule(),
        new AuthenticationModule(),
        new ServiceModule(),
        new InquiriesManagementModule(),
        new DataModule());        
    ApplicationPresenter applicationPresenter = 
        dependencies.Get<ApplicationPresenter>();
    Application.Run((Form)applicationPresenter.View);
}

程序(我现在如何使用静态依赖注入工厂)

[STAThread]
static void Main() {
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    DependencyInjectionFactory.Register(
        new ApplicationModule(),
        new AuthenticationModule(),
        new ServiceModule());
    ApplicationPresenter applicationPresenter = 
        DependencyInjectionFactory.Resolve<ApplicationPresenter>();
    Application.Run((Form)applicationPresenter.View);
}    

此外,我觉得拥有一个静态依赖注入器是一个肮脏的解决方案,因为它可以从任何地方调用,就像魔法一样......

是否把它推得太远,以至于我使事情复杂化而不是简化它们?

我做错了什么,DI或设计,我应该怎么做

你根本不需要条件绑定或上下文绑定。选择最简单的方法:对 nhibernate 配置、会话进行编程,...好像您没有运行时"计时"依赖项一样。这意味着:将经过身份验证的用户注入 NHibernate 配置构建器。

然后,您只需要确保在对用户进行身份验证之前,不实例化依赖于任何 NHibernate 的对象树部分。这应该很容易。

使用任何类型的引导机制,首先创建绑定,然后显示登录窗口,进行登录,成功登录后,加载(显示)应用程序的其余部分。

捆绑:

IBindingRoot.Bind<Configuration>().ToProvider<ConfigurationProvider>();
IBIndingRoot.Bind<ISessionFactory>()
            .ToMethod(ctx => ctx.Kernel.Get<Configuration>().BuildSessionFactory())
            .InSingletonScope();
public class ConfigurationProvider : IProvider<Configuration> 
{
    private readonly IUserService userService;
    public ConfigurationProvider(IUserService userService)
    {
        this.userService = userService;
    }
    public object Create(IContext context)
    {
        if(this.userService.AuthenticatedUser == null)
            throw new InvalidOperationException("never ever try to use NHibernate before user is authenticated! this includes injection an ISessionFactory in any class! Postpone creationg of object tree until after authentication of user. This exception means you've produced buggy code!");
        return Fluently.Configure()
            .DataBase(MsSqlConfiguration.MsSql2008)
                .ConnectionString(connectionBuilder => connectionBuilder.Is(... create the string...)
            ....
            .BuildConfiguration();
    }
}