【问题标题】:WPF/MVVM: Re-use a ViewModel in multiple Controller and Separation of ConcernsWPF/MVVM:在多个控制器中重用 ViewModel 和关注点分离
【发布时间】:2010-11-26 20:56:13
【问题描述】:

在我的 AdministrationController 中,我使用 PupilViewModel,例如:

_adminRepo.GetSchoolclassPupilList().ForEach(s =>
            {
                SchoolclassViewModel sVM = new SchoolclassViewModel(s, _adminRepo);

                foreach (PupilViewModel pVM in sVM.PupilListViewModel)
                {
                    pVM.Documents.DeleteDocumentDelegate += new Action<List<Document>>(OnDeleteDocument);
                    pVM.Documents.AddDocumentDelegate += new Action(OnAddDocument);
                    pVM.Documents.OpenDocumentDelegate += new Action<int, string>(OnOpenDocument);
                }
                SchoolclassList.Add(sVM);
            }); 

PupilViewModel 就是这样创建的:

public SchoolclassViewModel(Schoolclass schoolclass, IAdministrationRepository adminRepo)
        {
            _schoolclass = schoolclass;
            _adminRepo = adminRepo;  

            PupilListViewModel = new ObservableCollection<PupilViewModel>();
            schoolclass.Pupils.ForEach(p => PupilListViewModel.Add(new PupilViewModel(p, _adminRepo)));                      
        }

您肯定注意到 PupilViewModel 在其构造函数中采用 2 个参数。 重要的是第二个参数,它是一个服务/存储库,具体是它的 IAdministrationRepository 实例。

现在存在另一个名为 IncidentReportingControllerController。 在其构造函数中,我执行与 AdministrationController 中相同的操作:

// When I now try to wrap my pupils into a ViewModel I have a problem:

IEnumerable<Pupil> pupils = incidentRepo.GetPupilIncidentReportDocumentList();         
PupilViewModels = new ObservableCollection<PupilViewModel>(pupils.Select(p => new PupilViewModel(p, ???)));

A.) 我不想将 Service 传递给 PupilViewModel,因为没有理由更新 PupilViewModel 中的属性,因为它在 View 中是只读的。

B.) 在我的AdministrationController 中,我从Aggregation 的服务中获取数据: 1 Schoolclass 有 N Pupils 和 1 Pupil 有 N Documents。这些实体被包裹在 SchoolclassViewmodelsPupilViewModelsDocumentViewModels...

现在在我的IncidentController 中,我也从服务中获取数据,我的Aggregation 非常相似: 1 个Pupil 有N 个IncidentReports,1 个IncidentReport 有N 个Documents。这些实体被包裹在 PupilViewModelsIncidentReportViewModelsDocumentViewModels...

问题是 => 在 PupilViewModel 类中,它已经包装了一个 DocumentListViewModel。现在我需要再次使用 PupilViewModel 来包装 IncidentReportListViewModel,然后我再次有 1 个 Pupil 有 1 个 SeatingChair 并再次包装它们。这意味着我必须将三个服务传递给 PupilViewModel,尽管我并不总是需要它们。

我很难直截了当地解决这个问题,但不知何故我觉得这不是正确的方法。

那么我如何重用相同的 ViewModel,它包装了具有不同服务的不同聚合的实体?

【问题讨论】:

    标签: wpf mvvm viewmodel reusability


    【解决方案1】:

    在不知道自己走多远的情况下...我强烈建议您查看使用UnityPrism。如 Unitys 网站所述...

    Unity 应用程序块 (Unity) 是 轻量级的可扩展依赖 带支架的注射容器 构造函数、属性和方法调用 注射。

    在使用这些不同的框架时,您获得的是广泛的解耦和实例化的“职责”等。然后,您不再需要担心传递给构造函数的参数,因为框架会为您解析它们。

    例如,您还可以将已注册类型(例如 IDoSomethingController)的生命周期设置为实际类型 DoSomethingController...,并将其生命周期设置为单例(如果您需要在有人请求时传递单个实例) IDoSomethingController 类型的对象。

    一旦您使用了这些框架,您就不再“新建”一个实例,而是利用该框架来提供您正在寻找的重用。

    IDoSomethingController controller = IUnityContainer.Resolve<IDoSomethingController>();
    

    编辑:既然您说您使用的是 MEFedMVVM; DI 框架存在。您的 PupilViewModel 是 ObservableCollection 的一个实例。这太矫枉过正了,通过这个。在您离开之前,ViewModel 应该需要额外的重量来传递数据。您的 ViewModel 似乎试图代表对象而不是概念。我的意思是你可以简单地拥有一个 SchoolViewModel 来公开学生、班级等。这些项目成为模型,可以在你的 ViewModel 上以某种形式聚合。 ViewModel 旨在成为 View 的中介。它可以包含跨不同模型和服务的大量信息,成为视图的单点数据。

    【讨论】:

    • 嘿 Aaron,我已经在使用 MEFedMVVM,它提供了带有服务等的导入构造函数......我认为 Prism 在这里不会有更多帮助。我的问题是,PupilViewModel 是否可以获取 3 个服务参数?
    • @Lisa 服务在 MVVM 立场中只不过是数据访问点。因此,您的构造函数采用 3 个服务参数这一事实不是问题;只要这些服务是有目的的。
    • 是的,但是当我将相同的 ViewModel 包装 3 次并在每个不同的地方执行此操作时,我会注入 3 个服务,而我每次只需要一个服务,这对我来说似乎是一个 hack?!!!
    • 我没有使用过 MEF,但是我去查看了您所说的 MEFedMVVM,看起来解析实例的功能也部分通过 MEF。 GetService();这类似于我引用的 Container.Resolve() 。这意味着如果您在框架中使用 DI,您只需传递单个 ViewModel 需要的内容。您不需要将类型传递给其他类型,将它们推送到需要使用它的类型的构造函数中。上面还添加了额外的 cmets...
    • @Aaron 您编辑了文本并说:“我的意思是您可以简单地拥有一个公开学生、班级等的 SchoolViewModel。”我不能那样做,因为当 PupilViewModel 是绑定到一些文本框,用户更改数据并将文本框现在留在 PupilViewModel 中,我使用 AdministrationRepository.UpdatePupil(_pupil);如果我像你说的那样做,并且如果我理解正确,那么就没有 PupilViewModel,但是我的 Pupil 模型可以访问其中的服务,那就是 NoGo。实体从不调用数据访问方法。
    【解决方案2】:

    您似乎在视图模型上不必要地使用了存储库?我希望看到关于您的业务类型的存储库,而不是您的视图模型。

    例如...

    PubilsViewModel(ISchool school) { }
    

    学生视图模型只需要一个由 Unity(或您选择的 IOC)注入的学校实例,所有保存、更新等方法都存储在业务对象中。

    var school = new School();
    school.Save();
    school.Update();
    

    甚至是业务对象上的静态方法,它们在其构造函数中接受学校类型?无论哪种方式,我都希望您的视图模式能够调用业务对象上的方法,让 School 对象知道如何保存它的详细信息似乎更合乎逻辑(包括最终允许保存之前可能需要完成的验证到数据库)。

    在业务对象上,即您将拥有 School Repository 实例的地方,业务对象不需要知道它是如何被追求的机制,那就是交出存储库来担心,业务对象只需对自身执行任何业务规则验证,然后通过调用存储库上的方法来决定是否应将自身保存到数据库中。

    例子:

    public class School
    {
       private ISchoolRepository _repository;
    
       public string Name { get; set; }
    
       public School()
       {
          this._repository = IoC.Resolve<ISchoolRepository>();
       }
    
       public bool IsValid()
       {
         // Some kind of business logic?
         if (this.Name != null)
         {
           return true;
         }
    
          return false;
       }
    
       public void Save()
       {
          if (this.isValid())
          {
             this._repository.Save(this)
          }
    }
    

    存储库将处理将记录保存到数据库所需的任何代码。

    学校实体应该不知道如何保存自己,但没有理由不知道它需要什么的细节来保存自己...... ViewModel 不需要知道那个级别的细节(而且不应该),使用依赖注入,业务对象(School)只知道它需要一个 ISchoolRepository,实例化它,然后调用它的方法。

    这会解决你的问题...

    // Knows it needs a school.
    // When needing to start the save process for a School, would call the methods on the
    // school instance provided in the constructor.
    PupilViewModel(School school) { }
    
    // Knows it needs a Repository.
    // Would perform validation of business rules and call methods on the repository when//
    // it is ready to be pursisted.
    School(ISchoolRepository repository) {}
    
    // Repository
    // Would perform the read / write of the data.
    SchoolRepository() {}
    

    这有意义吗?希望你能看到你已经开始尝试和实现的关注点的分离......希望它有所帮助!

    【讨论】:

    • @BenjaminPaul 我已经在 Stackoverflow 上阅读了 1000 次,认为服务不应该在实体中使用。我使用我的模型作为 Poco。完全赤裸。没有 IDataErrorInfo、IPropertychanged、IEditableObject 等……随便。所有这些接口都是特定于 UI 的,因此它们位于 ViewModel 之下。你把我弄糊涂了……我必须再问一次,然后才能“关闭”这个帖子!
    • @BenjaminPaul 读到这个,这就是我的意思...:stackoverflow.com/questions/4292582/…
    • 我不认为你是对的,我很抱歉......这似乎支持我:efreedom.com/Question/1-3696186/…
    • @Lisa 在上面的这个例子中,我正在合并 SchoolDAO 和 School 业务对象的想法。在粗略的正常场景中,我会有一个 SchoolDAO,我会有一个这样的实例在业务对象上...业务对象将具有存储库的实例。
    • @Benjamin 我有一些关于如何构建 MVVM 应用程序的偶像,比如 jeremy Likeness、Ward Bell、Josh Smith 等......他们所有具有 viewModels 的应用程序都在从内部进行服务/存储库调用ViewModel... 所以我的做法肯定没有错。或者所有 alpha mvvm/wpf 极客都错了;-)
    【解决方案3】:

    我将回答我自己的问题,那些关注我的问题或在这里回答的人请测试/检查我的解决方案并评论它是否值得解决:

    起源问题是我在不同上下文的 3 个地方重用了 PupilViewModel。一次 PVM 的 ctor 需要 AdministrationService,而其他用途则需要 IncidentReportingService,最后一次是我忘记的第三个服务......

    现在在所有 3 个地方注入所有 3 个服务对我来说似乎很愚蠢:

    PupilViewModel pVM = new PupilViewModel(pupil,adminService,IncidentReportingService,3rdService);
    

    这样做 3 次看起来像糟糕的架构。其实我需要这个: 当我真的需要它们时,只需包裹学生并注入所有 3 项服务!!!

    http://marlongrech.wordpress.com/2010/05/23/mefedmvvm-v1-0-explained/

    向下滚动,您会看到 MEFedMVVM 可以进行 Ctor 和 Prop 注入!!! 完全忽略了该选项及其 MEF 默认注入(Property Injection)。

    编辑:这不起作用,因为:

    public SchoolclassViewModel(Schoolclass schoolclass, IAdministrationRepository adminRepo)
            {
                _schoolclass = schoolclass;
                _adminRepo = adminRepo;          
    
                //this.PropertyChangedHandler(object o, PropertyChangedEventArgs e)
                //{
                //    switch (e.PropertyName)
                //    {
                //        case "SchoolclassCode" : _saRepo.UpdateSchoolclass(_schoolclass); break;       
                //        default: break;
                //    }
                //}  
    
                PupilListViewModel = new ObservableCollection<PupilViewModel>();
                schoolclass.Pupils.ForEach(p => PupilListViewModel.Add(new PupilViewModel(p, _adminRepo)));
            }
    
            public IAdministrationRepository AdministrationRepository { get; set; }
    

    您看到 _adminRepo 是如何传递给新创建的 PupilViewModel 的吗?那时,当 Ctor 运行时,Property AdministrationRepository 仍然为空,以便从那里获取 Repo...不好:/

    【讨论】:

      猜你喜欢
      • 2016-07-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-09-13
      • 1970-01-01
      • 2011-07-17
      • 1970-01-01
      • 2011-07-22
      相关资源
      最近更新 更多