OJa*_*Jay 15 c# architecture asp.net entity-framework asp.net-mvc-4
这是一个与如何为中型到大型应用程序构建ASP.NET MVC项目相关的问题.
我以为我理解了MVC的概念,但在研究了中型和大型应用程序的架构后,我很困惑.(试图考虑可扩展性,可扩展性和持续维护)
当我试图按照"最佳实践"的指导方针(来自包括印刷和网络的众多来源)来思考如何构建应用程序时,我的困惑就出现了
试图尊重像
现在在创建小型(基本的,简单的)MVC应用程序时,所有这些都在同一个项目中完成(在这种情况下我说的是Visual Studio Project),MVC"Layers"之间的分离非常多只是VS项目中的文件夹(完全独立的命名空间).
对于我们的一些其他项目,我们采用了服务 - >存储库样式,因此这一点不会有任何不同.
我们使用Entity Framework作为数据库持久性(DB第一种方法).
我们将数据库访问(EF内容)分离到另一个VS项目中,因此我们在解决方案中有一个Web项目和模型(或数据)项目.
Web项目具有控制器和视图,数据项目具有服务,存储库和EF内容.
我的困惑在于模型(或者可能理解域模型与视图模型)
如果我试图遵循这种方法(我认为),我会有一个域模型(EF和存储库层处理的模型),然后我会有一个视图模型?(Controller和视图将处理的模型),现在这些不是90%相同吗?这种方式是否只是让你两次编写模型代码?我确定我读过控制器和视图不应该有域模型的地方?
我们接近它的一种方法是EF使其所有模型类都是部分的.然后我们扩展同一个类并向其添加一个MetaDataType类来创建"视图模型"(将DataAnnotations添加到属性中)然后本质上相同的模型将通过所有层传递,但这是"最佳"实践(那里在我看来是一个分裂,这是不对的)
例如
[MetadataType(typeof(Product_Metadata))]
public partial class Product
{
//Pretty much deliberately kept empty, just so
// the EF model class can have the attribute added
//The other side of this partial class is of course in the EF models
}
public class Product_Metadata
{
[Required]
[Display(Name = "Product name")]
public string Name { get; set; }
[Required]
[Display(Name = "Unit Cost")]
public decimal Cost { get; set; }
//etc... for the rest of the properties on the product EF model
}
Run Code Online (Sandbox Code Playgroud)
也许这是攻击它的"最佳"方式,但我之前没有遇到过这种方法.
我们将所有服务和存储库创建为接口,并使用结构映射作为IoC容器.我承认的另一件事是,即使我们使用依赖注入,我仍然在努力与TDD达成协议,感觉就像必须写两次所有(我认为DI的整点)
我想最终我对SO的意愿很有吸引力,因为他们比我更了解构建大型ASP.NET MVC应用程序以获得一些帮助和指导.似乎有大量的信息,但似乎都是非常概念性的.当我最终实现时,我迷失在概念中.
编辑
回应卡尔安德森先生
当我最终实现时,我迷失在概念中.
这些概念非常重要,但也很抽象.很难想象如何最好地构建解决方案直到它完成之后(即为时已晚),没有人能真正告诉你如何构建它,因为每个项目都是如此不同.
我会有一个域模型[...]然后我会有一个视图模型?[...]这些不是90%相同吗?
我相信这没关系.域模型描述实际对象(通常来自数据库).视图模型描述了视图正确呈现所有内容所需的信息.两者通常不包含逻辑,只包含属性列表.我认为这几乎是完全相同的.使用Automapper可以轻松地在模型之间进行映射.
控制器和视图不应该具有域模型?
大多数开发人员更喜欢这种方法 我应该这样做.视图应该是一个视图模型,如果需要,控制器可以简单地在模型之间进行映射.
EF使其所有模型类都是部分的.然后我们扩展同一个类并为其添加一个MetaDataType类以生成"视图模型"
这是一个有趣的方法,但我不能推荐它.复制模型是可以接受的.
TDD,感觉必须要写两次
是的,它可以.您正在采用的方法将抽象和实现分开很多.它确实让人感觉有更多的东西要写,但它也更容易理解.特别是因为您使用接口而不是实现进行通信.
我呼吁提供一些帮助和指导
虽然你提到了TDD,但我不记得你提到过洋葱建筑.请务必阅读Jeffrey Palermo的The Onion Architecture.
观看Jimmy Bogard 让你的控制器节食.
您提出了一些有趣的问题和观点,关于构建ASP.NET MVC应用程序的无数方法,更不用说任何应用程序了.我可以为您提供有关该主题的想法,您可以随意使用它.
你说你担心在创建域模型和视图模型时你会做两次相同的事情,因为它们看起来几乎相同.让我给你一些可能让你改变主意的场景:
List<Category>,但您不希望您的域模型必须跟踪分页的元数据,例如总记录,页面大小和页码,对吗?A CategoryListViewModel会解决这个问题.IsAdmin布尔值; 如果你使用域模型来填充视图,那么如果一个坏用户发现某个IsAdmin属性存在并传入IsAdmin=true了查询字符串,那么模型绑定器会选择它并使该帐户成为管理员,即使该视图从未显示过允许更改该值的字段.如果绑定到不包含该视图模型的视图模型IsAdmin 属性,而只是与视图相关的数据,然后这个漏洞就不存在了.int),然后直接将您的域模型绑定到您的视图将需要视图解析和格式化那个int价值.相反,视图模型可以将电话号码保存为字符串,已经正确格式化并只显示值.PagedAccountViewModel,AddNewAccountViewModel,UpdateAccountViewModel,等.没有最佳实践/架构。每种设计都有缺点。关于您的目标架构和 90% 的代码重复,这是我的想法。它分为实体(DTO/模型)或服务/存储库。
我通常遵循的基本概念是N层架构设计。它基本上被表述为“将域/业务层与其他层(UI /数据访问)分开。主要目标是,当您的应用程序迁移到其他系统(UI /存储)时,业务层保持不变。
如果您将 95% 的领域逻辑放入业务层(其他 5 个可能在数据库中,例如事务/报告生成),那么您几乎不需要更改任何内容,并且仍然具有相同的领域规则。领域层的问题解决了,你只需要专注于UI或存储(存储库)。
通常,N层的结构如下:
entity
interfaces
DataAccess | BusinessLogic
UI
Run Code Online (Sandbox Code Playgroud)
每层都由程序集(项目/解决方案)分隔开,因此不强调每层之间的耦合。
现在想象一个常见的“操作消息”类。我想象这个班级是这样的:
public class OperationMessage{
public bool IsError{get;set;}
public string OperationMessage{get;set;}
}
Run Code Online (Sandbox Code Playgroud)
随意修改类,添加枚举以进行警告等(这是使用自动属性的代码味道,如果您进行维护,请不要遵循)。
假设您的MVC应用程序具有名为“message_error”的 css,其中具有color:red;和font-weight:bold;属性。通常您希望将其分配给具有诸如 之类的属性的类CssClassName。您有 3 个选择:
OperationMessage修改实体层的基础
这是最容易做的事情。然而,你打破了n层架构,因为现在你的业务层有了关于“css”的知识,它指的是类似web的架构。它添加特定于 ui 的逻辑(分配CssClassName业务层)。如果有一天你想将它迁移到C# Winform或者Windows Mobile/ Azure,它会污染架构。
添加一个名为的新类WebOperationMessage
这就是我所说的ViewModel。现在它变得重复了,因为它与类有 90% 相似OperationMessage。但您的 N 层架构保持有序。OperationMessage从业务层获取对象后,您需要进行一些转换。这种转换,就是我所说的Presentation Logic。
继承OperationMessage类
对于实体类型类来说,这可能是更好的方法。它确保您的 N 层架构保持有序,并且不会重复 90% 的代码。我还没有发现这个设计的缺陷,但也许有任何defensive-code风格的实体。但是,您仍然需要进行转换。
这些服务已经在界面中重复。然而,正是由于实现了N-Tier架构,创建了持久性无知的代码。它使他们更容易进行单元测试和模拟。我希望读者已经了解模拟和单元测试,所以这个答案仍然相关。
但是,如果您不进行单元测试或模拟,那么这种分层或重复值得付出努力吗?正如文章中所引用的那样,
选择是您是否想要构建分层应用程序。如果想要层次感,分离一定要严格。如果不是,则它不是分层应用程序。
简而言之,一旦你违反/破坏了分层,你就失去了可移植性。如果您违反了 BLL / DAL 分层,您就失去了更改存储库的灵活性。如果您违反了 BLL / PL,您就失去了将应用程序从一种类型的 UI 迁移到另一种类型的灵活性。
这值得么?在某些情况下,是的。在其他情况下,例如企业应用程序,通常比较刚性,并且不太可能发生迁移。然而,很多时候企业可以扩张,需要流动性。所以,动物园管理员必须成为护林员。
我的2分钱