gpt4 book ai didi

c# - MVC/MVVM/分层中的ViewModels-最佳做法?

转载 作者:行者123 更新时间:2023-12-02 07:48:46 27 4
gpt4 key购买 nike

我对使用ViewModels还是相当陌生,我想知道,是否可以将ViewModel包含域模型的实例作为属性,还是应该将那些域模型的属性作为ViewModel本身的属性?例如,如果我有一个Album.cs

public class Album
{
public int AlbumId { get; set; }
public string Title { get; set; }
public string Price { get; set; }
public virtual Genre Genre { get; set; }
public virtual Artist Artist { get; set; }
}

通常是让ViewModel持有 Album.cs类的实例,还是让ViewModel为 Album.cs类的每个属性都具有属性。
public class AlbumViewModel
{
public Album Album { get; set; }
public IEnumerable<SelectListItem> Genres { get; set; }
public IEnumerable<SelectListItem> Artists { get; set; }
public int Rating { get; set; }
// other properties specific to the View
}


public class AlbumViewModel
{
public int AlbumId { get; set; }
public string Title { get; set; }
public string Price { get; set; }
public IEnumerable<SelectListItem> Genres { get; set; }
public IEnumerable<SelectListItem> Artists { get; set; }
public int Rating { get; set; }
// other properties specific to the View
}

最佳答案

tl; dr

Is it acceptable for a ViewModel to contain instances of domain models?


基本上不是因为您实际上是在混合两层并将它们绑在一起。我必须承认,我看到它经常发生,并且在某种程度上取决于您的项目的双赢级别,但是我们可以声明它不符合 SOLID的单一责任原则。

有趣的部分:这不仅限于MVC中的 View 模型,实际上是 good old data, business and ui layers分离的问题。我将在稍后说明,但现在;请记住,它适用于MVC,但同时也适用于许多其他设计模式。
我将首先指出一些适用的概念,然后在以后放大一些实际方案和示例。

让我们考虑不混合各层的一些利弊。
这会花你多少钱
总会有一个陷阱,我将对它们进行总结,稍后再解释,并说明为什么它们通常不适用
  • 重复代码
  • 增加了额外的复杂性
  • 额外的性能命中

  • 你会得到什么
    总会有胜利,我将总结一下,稍后再解释,并说明为什么这实际上有意义
  • 层的独立控制

  • 费用

    重复代码

    It's not DRY!


    您将需要一个附加的类,它可能与另一个完全相同。
    这是一个无效的参数。不同的层具有明确定义的不同目的。因此,位于一层中的属性与另一层中的属性具有不同的用途-即使这些属性具有相同的名称!
    例如:
    这不是重复自己:
    public class FooViewModel
    {
    public string Name {get;set;}
    }

    public class DomainModel
    {
    public string Name {get;set;}
    }
    另一方面,两次定义映射将重复您自己:
    public void Method1(FooViewModel input)
    {
    //duplicate code: same mapping twice, see Method2
    var domainModel = new DomainModel { Name = input.Name };
    //logic
    }

    public void Method2(FooViewModel input)
    {
    //duplicate code: same mapping twice, see Method1
    var domainModel = new DomainModel { Name = input.Name };
    //logic
    }

    It's more work!


    真的吗如果您开始编码,则将有超过99%的模型重叠。抢一杯咖啡将花费更多时间;-)

    "It needs more maintenance"


    是的,确实如此,这就是为什么您需要对映射进行单元测试的原因(请记住,不要重复映射)。
    增加了额外的复杂性
    不,不是的。它增加了一个额外的层,这使它更加复杂。它不会增加复杂性。
    我的一个聪明的 friend 曾经这样说过:

    "A flying plane is a very complicated thing. A falling plane is very complex."


    He is not the only one using such a definition的区别在于可预测性,它与熵( chaos的度量)有实际关系。
    通常, 模式不会增加复杂性。它们的存在是为了帮助您减少的复杂性。它们是众所周知的问题的解决方案。显然,实现不当的模式无济于事,因此您需要在应用模式之前了解问题。忽略问题也无济于事;它只是增加了技术债务,必须在某些时候偿还。
    添加一层可以为您提供明确定义的行为,由于明显的额外映射,行为将更加复杂。进行更改时,用于各种目的的混合层将导致无法预料的副作用。重命名数据库列将导致UI中的键/值查找不匹配,从而使您进行不存在的API调用。现在,考虑一下这以及它与调试工作和维护成本之间的关系。
    额外的性能打击
    是的,额外的映射将导致消耗更多的CPU能力。但是,与从数据库中获取数据相比,这可以忽略不计(除非您将树莓派连接到远程数据库)。底线:如果这是一个问题:使用缓存。
    胜利

    层的独立控制
    这是什么意思?
    此(以及更多)的任意组合:
  • 创建可预测的系统
  • 在不影响用户界面的情况下更改业务逻辑
  • 在不影响您的业务逻辑的情况下更改数据库
  • 更改用户界面,而不会影响数据库
  • 能够更改您的实际数据存储
  • 完全独立的功能,隔离的可测试行为,易于维护
  • 应对变化并增强业务能力

  • 本质上:您可以通过更改定义良好的代码来进行更改,而不必担心讨厌的副作用。
    当心:企业应对措施!

    "this is to reflect change, it's not going to change!"


    变化将来: spending trillions of US dollar annually无法简单地过去。
    很好。但是作为开发人员面对现实;没有出错的那一天就是您停止工作的那一天。同样适用于业务需求。
    fun fact; software entropy

    "my (micro) service or tool is small enough to cope with it!"


    这可能是最困难的一项,因为这里确实有一个好处。如果您开发的东西只能使用一次,那么它可能根本无法应付更改,并且您必须重新构建它,前提是您实际上要重用它。然而,对于所有其他事情:“变革将来临”,那么为什么要使变革变得更加复杂呢?并且,请注意,可能在简约工具或服务中遗漏了一些层通常会使数据层更靠近(用户)接口(interface)。如果您使用的是API,则您的实现将需要更新版本,该版本需要在所有客户端之间分发。您可以在一次茶歇期间这样做吗?

    "lets do it quick-and-simple, just for the time being...."


    您的工作是暂时的吗?开玩笑;-)但是;您什么时候要修复它?可能是因为您的技术债务迫使您这样做。那时,您花的钱比短暂的喝咖啡休息时间还多。

    "What about 'closed for modification and open for extension'? That's also a SOLID principle!"


    是的!但这并不意味着您不应该修正错字。或者每个应用的业务规则都可以表示为扩展的总和,或者不允许您修复已损坏的问题。或如 Wikipedia所述:

    A module will be said to be closed if it is available for use by other modules. This assumes that the module has been given a well-defined, stable description (the interface in the sense of information hiding)


    实际上促进了层的分离。

    现在,一些典型方案:
    ASP.NET MVC

    因为,这是您在实际问题中使用的:
    让我举个例子吧。想象一下以下 View 模型和域模型:
    注释:这也适用于其他图层类型,仅举几例:DTO,DAO,实体,ViewModel,Domain等。
    public class FooViewModel
    {
    public string Name {get; set;}

    //hey, a domain model class!
    public DomainClass Genre {get;set;}
    }

    public class DomainClass
    {
    public int Id {get; set;}
    public string Name {get;set;}
    }
    因此,在 Controller 中的某个位置填充FooViewModel并将其传递给 View 。
    现在,请考虑以下情形:
    1)领域模型改变。
    在这种情况下,您可能还需要调整 View ,这在关注点分离的情况下是一种不好的做法。
    如果已将ViewModel与DomainModel分开,则在映射中进行较小的调整(ViewModel => DomainModel(并返回))就足够了。
    2)DomainClass具有嵌套的属性,您的 View 仅显示“GenreName”
    我已经看到在实际的现场情况下这出了问题。
    在这种情况下,一个常见的问题是 @Html.EditorFor的使用将导致嵌套对象的输入。这可能包括 Id和其他敏感信息。这意味着泄漏实现细节!您的实际页面与域模型相关联(可能与域模型相关联)。学习完本类(class)后,您将发现自己正在创建 hidden输入。如果将其与服务器端模型绑定(bind)或自动映射器结合使用,将变得越来越难以使用firebug之类的工具来阻止对隐藏的 Id的操作,或者忘记在属性中设置属性,这将使其在您的 View 中可用。
    尽管有可能(也许很容易)阻止其中一些字段,但是您拥有的嵌套的Domain/Data对象越多,正确设置此部分的难度就越大。和;如果您在多个 View 中“使用”此域模型怎么办?他们会表现得一样吗?此外,请记住,您可能由于不一定要针对 View 的原因而想要更改DomainModel。因此,对DomainModel进行的每次更改都应注意,这可能会影响 Controller 的 View 和安全性。
    3)在ASP.NET MVC中,通常使用验证属性。
    您是否真的要您的域包含有关您的 View 的元数据?还是将 View 逻辑应用于您的数据层?您的 View 验证是否始终与域验证相同?它是否具有相同的字段(或其中一些是串联的)?它具有相同的验证逻辑吗?您正在使用跨域应用程序吗?等等。
    我认为这显然不是走的路。
    4)更多
    我可以为您提供更多方案,但这只是更吸引人的口味问题。我只是希望现在您能明白这一点:)不过,我答应了一个例子:
    Scematic
    现在,对于真正的肮脏和快速赢家,它会起作用,但我认为您不想要它。
    建立 View 模型只需要付出更多的努力,通常与域模型相似,即80%以上。感觉好像在做不必要的映射,但是当出现第一个概念差异时,您会发现值得付出努力:)
    因此,作为替代方案,我为一般情况建议以下设置:
  • 创建一个 View 模型
  • 创建域模型
  • 创建数据模型
  • 使用像automapper这样的库来创建一个到另一个的映射(这将有助于将Foo.FooProp映射到OtherFoo.FooProp)

  • 好处是,例如;如果您在其中一个数据库表中创建一个额外的字段,则不会影响您的 View 。它可能会击中您的业务层或映射,但在那里会停止。当然,大多数情况下,您也想更改 View ,但是在这种情况下,您不需要更改 View 。因此,它将问题隔离在代码的一部分中。
    Web API/数据层/DTO
    首先要注意: here's关于如何在某些情况下可以省略DTO(不是 View 模型)的一篇不错的文章-在我的实际方面完全同意;-)
    在Web-API/ORM(EF)方案中如何工作的另一个具体示例:
    在这里它更加直观,尤其是当使用者是第三方时,您的域模型不太可能与您的使用者的实现相匹配,因此 View 模型更可能是完全独立的。
    Web Api Datalayer EF
    note :名称“域模型”有时与DTO或“模型”混合使用
    请注意,在Web(或HTTP或REST)API中;通信通常由数据传输对象(DTO)完成,该对象是HTTP端点上公开的实际“内容”。
    因此,您可能会问我们应该将这些DTO放在哪里。它们在领域模型和 View 模型之间吗?嗯,是;我们已经看到将它们视为 viewmodel会很困难,因为消费者可能会实现自定义 View 。
    DTO是否可以替换 domainmodels还是他们有理由独立存在?通常,分离的概念也将适用于 DTO'sdomainmodels。但是再说一遍:你可以问自己(这就是我有点务实的地方);域中是否有足够的逻辑来明确定义 domainlayer?我想您会发现,如果您的服务越来越小,则 logic的一部分实际的 domainmodels也将减少,并可能被排除在外,最终您将得到: EF/(ORM) EntitiesDTO/DomainModelConsumers
    免责声明/注释
    正如@mrjoltcola所说:还需要牢记组件过度设计。如果以上都不适用,并且可以信任用户/程序员,那您就很好了。但是请记住,由于DomainModel/ViewModel的混合,可维护性和可重用性将降低。

    关于c# - MVC/MVVM/分层中的ViewModels-最佳做法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23648832/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com