Simple Injector在BaseClass中注入多个依赖项

Bal*_*ngh 0 c# dependency-injection inversion-of-control mvvm simple-injector

我有一个BaseViewModel由多个ViewModel类继承的.在我,BaseViewModel我有几个依赖注入ViewModel.现在,如果我需要添加一个新的依赖项,我BaseViewModel需要更改所有继承的VM BaseViewModel.请告诉我如何在Simple Injector中处理它.以下是我的代码结构:

如何使我的基类注入独立,以便我不需要在所有继承的类中进行更改?

码:

public class BaseViewModel
{
    protected readonly IAESEnDecrypt AESEnDecrypt;
    protected readonly IDataService DataService;
    protected readonly INavigationService NavigateToPage;
    public BaseViewModel(INavigationService nav, IDataService data, IAESEnDecrypt encrypt)
    {
        AESEnDecrypt= encrypt;
        NavigateToPage = nav;
        DataService = data;
    }
}


public class ViewModel
{
   public ViewModel(INavigationService nav, IDataService data, IAESEnDecrypt encrypt) : base (nav, data, encrypt)
   {

   }
}
Run Code Online (Sandbox Code Playgroud)

我的BaseViewModel包含以下一些接口,其实现通过构造函数注入:

- NavigationService
- DataService
- GeoLocationService
- SmartDispatcher
- MessageBus which implement Message Aggregator
Run Code Online (Sandbox Code Playgroud)

它还包含一些常见属性作为静态变量,其数据在整个应用程序中使用,如UserDetails.并且还包含CancellationToken,IsBusy以显示进度条.

BaseViewModel还包含HandleException方法,该方法处理来自所有ViewModel的所有传入异常.还包含一些常用命令,这些命令在所有视图中使用,如Si gnoutCommand,NavigationBar Commands.

实际上它已经开始包含各种ViewModel中使用的各种常用方法.

请建议我如何重构此代码?

Ric*_*Net 8

你的最后一句话:

实际上它已经开始包含各种ViewModel中使用的各种常用方法

准确描述你的问题!正如Steven所描述的那样,您通过一个基类构建了几乎完整的应用程序.从而侵犯了你现在正在经历的开放原则.

诀窍是围绕非常小的SOLID ViewModels设计应用程序,在运行时组成应用程序.通过拆分ViewModel并使用a UserControl作为您的视图,您可以为用户创建大的复杂视图,同时您仍然可以从使用SOLID设计中获得所有好处.那么让我们来看看你实现的一些不同的接口以及你在基类中"处理"的一些函数:

的NavigationService

这听起来像是一个控制应用程序流程的服务.这听起来像你的主视图(模型).你可以创建一个单独的MainViewModel属性,假设CurrentView您正在使用WPF,通常会将此属性绑定到ContentControl.此控件的内容可以是从单个TextBlock到完整的所有内容UserControl.UserControls仍然可能非常复杂,因为它们可能由多个子用户控件组成,依此类推.使用MVVM框架(例如Caliburn MicroMVVM Light)可选,但会派上用场.

它也可以是具有某种回调或委托功能的应用程序全局服务,以执行到某个视图(模型)的导航.在任何情况下,应用程序的基础结构部分都应该拥有自己的类,不应该放在基类中.

DataService的

单个数据服务是我工作超过10年的方式.我每次都撞到墙上.有一个时间点,您需要一些特殊的东西,这些东西不包含在您的数据服务中,您可能会通过完整的代码库进行正确的调整.说到开放原则......

比我了解了Command/Handler和Query/Handler模​​式.你可以在这里这里读到这个.在所有需要数据的地方使用此模式,您只需注入正确的IQueryHandler <,>并在那里使用它.并非每个视图(模型)都需要数据,当然也不需要相同的数据.那么为什么要使用全局DataService呢?这也将改善您的DBContext对象的生命周期管理.

HandleException

为什么您的基类负责处理viewmodel的异常?基类知道这些异常的含义是什么?基类有什么作用?记录异常,向用户显示一条消息(什么样的消息?)并静默继续?让应用程序在3分钟后崩溃并让用户不知道发生了什么?如果您没有预料到它们会被抛出,那么就不应该捕获IMO异常.然后在应用程序级别(例如在您的应用程序级别Main)记录异常,向用户显示"对不起"消息并关闭应用程序.如果你期望一个例外,那就在那里处理然后处理.

的UserDetails

问自己一个问题,你的40个ViewModel中有多少人确实需要这些信息?如果所有40个人都需要这些信息,那么您的设计还有其他问题.如果没有,只IUserContext在实际使用它们的ViewModels中注入这些细节(甚至更好).

如果您将它用于某种身份验证,请考虑使用包装任务的装饰器,他们需要执行它的权限.

IsBusyIndi​​cator

再说一次:你是否在每个ViewModel中都需要这个?我想不是.我认为此外,向用户显示忙碌指示符是View的责任,而不是ViewModel,并且随着任务的长度确定您是否需要显示此项,请将其作为任务的责任(假设您正在查看您也可以通过使用已经提到的Command/Handler模​​式以SOLID方式执行任务.

使用WPF,您可以定义一个可以绑定到视图的依赖项属性,从而显示某种繁忙指示符.现在只需注入一个ShowBusyIndicatorService需要显示它的任务.或者将所有(冗长的)任务包装在一个ShowBusyIndicatorDecorator.

设计

现在让我们看一下您可以定义的一些简单接口来构建View(Model).假设我们决定让每个ViewModel负责一个任务,并定义以下(典型的LoB)任务:

  • 显示(任何类型)数据
  • 选择或选择数据
  • 编辑数据

可以将单个任务拆分为"显示单个数据类型(实体)的数据".现在我们可以定义以下接口:

  • IView<TEntity>
  • ISelect<TEntity>
  • IEdit<TEntity>

对于每种接口类型,您将根据您的语义首选项创建处理器/服务或DialogHandler,这将执行典型的MVVM操作,例如查找相应的视图并将其绑定到viewmodel并以某种方式显示(模式窗口,将其作为usercontrol注入)在一些内容控制等).

通过在您需要导航或显示不同视图的"父"ViewModel中注入此单个Processor/Service或DialogHandler,您可以通过单行代码显示任何类型的实体,并将职责转移到下一个ViewModel.

我现在在项目中使用这3个接口,我真的可以做我过去可以做的一切,但现在以SOLID方式.我的EditProcessor,接口和viewmodel看起来像这样,从所有不那么有趣的东西中删除.我正在使用Caliburn Micro进行ViewModel-View绑定.

public class EditEntityProcessor : IEditEntityProcessor
{
    private readonly Container container;
    private readonly IWindowManager windowManager;

    public EditEntityProcessor(Container container, IWindowManager windowManager)
    {
        this.container = container;
        this.windowManager = windowManager;
    }

    public void EditEntity<TEntity>(TEntity entity) where TEntity : class
    {
        // Compose type
        var editEntityViewModelType = 
        typeof(IEntityEditorViewModel<>).MakeGenericType(entity.GetType());

        // Ask S.I. for the corresponding ViewModel, 
        // which is responsible for editing this type of entity
        var editEntityViewModel = (IEntityEditorViewModel<TEntity>)
                 this.container.GetInstance(editEntityViewModelType);

        // give the viewmodel the entity to be edited
        editEntityViewModel.EditThisEntity(entity);

        // Let caliburn find the view and show it to the user
        this.windowManager.ShowDialog(editEntityViewModel);
    }
}

public interface IEntityEditorViewModel<TEntity> where TEntity : class
{
    void EditThisEntity(TEntity entity);
}

public class EditUserViewModel : IEntityEditorViewModel<User>
{
    public EditUserViewModel(
        ICommandHandler<SaveUserCommand> saveUserCommandHandler,
        IQueryHandler<GetUserByIdQuery, User> loadUserQueryHandler)

    {
        this.saveUserCommandHandler = saveUserCommandHandler;
        this.loadUserQueryHandler = loadUserQueryHandler;
    }

    public void EditThisEntity(User entity)
    {
        // load a fresh copy from the database
        this.User = this.loadUserQueryHandler.Handle(new GetUserByIdQuery(entity.Id));
    }

    // Bind a button to this method
    public void EndEdit()
    {
        // Save the edited user to the database
        this.saveUserCommandHandler.Handle(new SaveUserCommand(this.User));
    }

    //Bind different controls (TextBoxes or something) to the properties of the user
    public User User { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

从您IView<User>现在可以修改与该行代码当前所选的用户:

// Assuming this property is present in IView<User>
public User CurrentSelectedUser { get; set; }

public void EditUser()
{    
    this.editService.EditEntity(this.CurrentSelectedUser);
}
Run Code Online (Sandbox Code Playgroud)

请注意,通过使用此设计,您可以将ViewModel包装在装饰器中,以执行横切关注,例如日志记录,身份验证等.

所以这是一个很长的答案,最短的答案是:松散基类,它咬你,它会咬你越来越难!