如何在MVC项目之外重构数据库访问代码,但将视图模型保留在内部

本文关键字:代码 视图 在内部 保留 模型 访问 数据库 MVC 项目 重构 | 更新日期: 2023-09-27 18:20:50

我有一个asp.net-mvc网站,包含以下文件夹:

  • 控制器
  • 脚本
  • 视图
  • ViewModels
  • 型号
  • DomainModel

我现在想在另一个.net应用程序(一个windows控制台应用程序,所以根本不是web)中访问很多这种业务逻辑、数据库访问代码和数据,所以我正在重构,以尽可能多地删除MVC项目之外的东西,并将其添加到解决方案中的其他项目中,这样代码就可以与其他解决方案共享。

我有两个主要问题;

  1. 我的主要问题是,我很难找到一个地方来放置生成ViewModel的代码,因为当控制台应用程序发送需要与视图中相同数据的电子邮件时,我想在控制台应用程序中重用很多代码。

  2. 另一个主要问题是,当我的许多实例化视图模型的函数都是从一堆数据库访问代码开始时,我很难看到如何将我的数据库访问代码从MVC项目中移出,同时仍然将视图模型放在里面。

到目前为止,这里有一些细节和我的流程:

步骤1-将DomainModel移到另一个项目-成功

因此,移动DomainModel项目很简单(因为这是很多原始对象,上面有一些业务逻辑——没有任何web)。

步骤2-精简控制器-成功

我已经精简了尽可能多的控制器,并将任何业务逻辑或复杂的数据访问逻辑移到Models文件夹中。当我试图将models文件夹移到MVC项目之外时,出现了一些问题:

步骤3-尝试将Models文件夹移出MVC项目-斗争

在细化控制器的过程中,我有许多不同的控制器操作,这些操作转到模型类并返回我传递回视图的ViewModel。类似这样的东西(在我的控制器类中):

 public ActionResult ApplicationDetail(int id)
 {
      AppDetailViewModel applicationViewModel = Model.GenerateAppDetailViewModel(id);
      return View(applicationViewModel);
 }

因此,我的Model文件夹中的文件依赖于ViewModel类。我确实想集中GenerateAppDetailViewModel()函数,因为它在多个不同的控制器中使用。而且在我的控制台应用程序(它发送电子邮件)中,我经常想获得碰巧在某个视图上的所有数据,所以我的代码也"想"利用视图模型。如果我把它从MVC项目中移出,那么我可以重用,但我认为存在依赖性问题(很明显,我的控制台应用程序中不需要SelectListItem,但在其他情况下,它们只是生成视图所需的不同数据的容器对象,我确实想重用)

或者另一件事打破了对的依赖

System.Web.Mvc

因为我有很多代码:

  1. 查询数据库中的表
  2. 将其转换为对象集合(我使用的是nhibernate)
  3. 将其转换为某个DTO对象(位于ViewModels文件夹中)或SelectListItem对象列表(用于填充视图中的下拉列表),后者是System.web.vc的一部分

我想寻找打破这种依赖关系的最佳方式的建议,这样我就可以从MVC项目中移出尽可能多的代码进行重用。

问题是,如果我试图将我的ViewModel代码吸入Model文件夹和另一个项目,那么我会再次陷入困境,因为ViewModel类对有很大的依赖性

System.Web.Mvc

由于SelectListItem之类的原因。

我应该有两个视图模型文件夹吗(一个在MVC项目中,有特定的system.web.MVC引用,另一个在不同的项目中?)。对SelectListItem的依赖似乎是导致争用的原因

在我看到的大多数例子中,ViewModels确实依赖于System.Web.Mvc,比如本教程

我看到了这些问题:

  • Asp.Net MVC SelectList重构问题
  • selectlist逻辑在ASP.NET MVC、视图、模型或控制器中应该位于什么位置

它们有点相关,但不确定它们是否回答了我提出的具体的整体重构问题。

如何在MVC项目之外重构数据库访问代码,但将视图模型保留在内部

视图模型是特定于特定应用程序的。我想视图模型在您的web应用程序和控制台应用程序之间会有所不同。因此,让每个应用程序定义自己的视图模型以及域模型和视图模型之间的对应映射。不要让你的域模型拥有将它们转换为视图模型的方法,因为这样你就把域层完全绑定到了UI层,这是最糟糕的事情。使用映射层(将针对每种应用程序类型)。AutoMapper是一个很好的映射层示例。

甚至不要尝试在控制台应用程序中重用ASP.NET MVC视图模型。正如您已经发现的,它们将包含对System.Web.Mvc的引用,因为例如,ASP.NET Mvc中的dropDownList是用IEnumerable<SelectListItem>类表示的,而在控制台应用程序中,天知道,可能是IEnumerable<SomeItemViewModel>

结论:视图模型以及域和视图模型之间的来回映射属于UI层(也称为ASP.NET MVC、Console、WPF…)。

我理解你想要实现什么,我必须告诉你:在不同的UI层中重用相同的ViewModel是非常正常的。毕竟,VM是MVVM模式的一部分,而该模式是关于分离关注点的。而且,分离意味着能够用另一种实现替换较低级别的层。单独软件层(以及其他层)的目的。

  1. 首先,您必须假设您的MVC web项目是基于MVVM的。这将在心理上帮助你做出正确的决定。我想你已经这样做了,因为你使用了ViewModel术语
  2. 使ViewModels独立于平台。这是可能的,与SelectListItem无关。毕竟,SelectListItem只包含选项text/value对,以及是否选中的标志。很明显,你可以用不同的方式表达相同的信息。在将这种Generic类型的ViewModel传递给MVC之后,您可以将Generic"SelectListItem"转换为MVC SelectListItem。是的,这是一种映射,它是在MVC视图中发生还是在将其传递给视图之前发生都无关紧要。但它必须发生在UI层(MVC web项目)上,因为这是一个特定于平台的映射问题
  3. 您提到了数据访问代码:这是一个单独的软件层,通常抽象地注入ViewModels中。我预计这部分不会有任何问题

因此,您最终将拥有不同的软件层(很可能在不同的.NET库中):数据访问层、ViewModel层、MVC Web层。

MVC项目中也有可能定义MVC ViewModels(从Controller传递到View的ViewModels),但这些只是简单地处理(可能接受)通用ViewModels,并在MVC视图特定的术语中公开内容(映射、继承、你的想象?)。这也很正常——毕竟,MVC是一个基于web的UI,它肯定有特定于平台的差异,必须在平台级别处理,而不是以前。MVC特定的ViewModel将是处理从泛型到MVC SelectListItem的映射的好地方。

一旦完成了重构,就无法阻止您通过使用与其他UI层相同的通用ViewModels来实现另一个UI层-您提到的控制台应用程序-MVC Web项目。在控制台应用程序中使用通用ViewModels时,如果您面临控制台平台特定的问题,您可以按照与我上面针对MVC特定的ViewModels所解释的相同的方式来提出平台特定的ViewModel。

您可以使用扩展方法在控制器中创建视图模型:

控制器:

 public ActionResult ApplicationDetail(int id)
 {
      var model = _serviceLayer.GetSomeModel(id); 
      var viewModel = model.CreateInstance(model);      
      return View(viewModel);
 }

在你的mvc项目中创建这个SomeModelExtensions

public class SomeModelExtensions {
      public AppDetailViewModel CreateInstance(this SomeModel model) {
           var viewModel = new AppDetailViewModel();
           // here you create viewmodel object from model with logic
           return viewModel;
      }
}

通常,在布局MVC应用程序时,我会使用以下设置/架构,在那里我可能需要重用模型:

MVC项目:所有与网络相关的东西。我在这里定义ViewModels,并将它们映射到域模型。

模型项目:带有所有域逻辑的类库。

Repository项目:这是一个访问数据库和域模型的类库。

它的工作方式是MVC项目将使用(希望是注入的)Repository库来获取域模型并将其映射到自己的ViewModel。

如果您也想分离贴图,可以将"贴图层"(mapping Layer)放在一个单独的项目中,就像其他人建议的那样(最终使用AutoMapper)。映射层将引用存储库(和域模型),而MVC应用程序将仅引用映射层。

问题是,在创建ViewModels时,映射层将需要对System.Web.Mvc的引用,正如您发现的那样,而您无法对此进行转义。这就是为什么其他人说,我也同意,每个项目都应该有一个映射层。

解决这一问题的一个好方法是有一个更通用的映射层,其中包含为常见情况(如电子邮件)定义的映射类。然后,在特定的子类中,您可以定义特定情况的映射(比如依赖System.Web.Mvc的情况)

因此,最终堆栈将类似于以下内容,依赖关系将下降。当然,一切都应该基于接口。

    MVC App                      Console App
       |                            |
       |                            |
   MVC Specific Mapper         Console Specific Mapper
                 '               /  
                  '             /
                   '           /   
                   GenericMapper <- EmailMapper and EmailViewModel can be implemented here
                     |       |      
                     |       |
                Repository   |
                     |       |
                     |       |
                    DomainModels  

以上内容并非毫无困难,如果只有一两种常见情况,那么将映射拆分的努力可能不值得。这样,从Generic Mapper向下,您就可以自由地使用System.Web.Mvc库,而在上面,您可以自由地忘记DB访问代码(从某种意义上说,Mapper将充当应用程序的存储库)。

我认为MVC web将使用与控制台应用程序相同的数据+额外的字段,对吧?

那么,从Model继承ViewModel呢?这样,您就可以重用模型,并根据需要在ViewModel上获得自定义字段。

public class AppDetailModel
    {
        public int ID { get; set; }
        public string Name { get; set; }
    }

    public class AppDetailViewModel : AppDetailModel
    {
        public string ViewProperty { get; set; }
    }