MVC/MVVM/分层中的ViewModels-最佳做法? [英] ViewModels in MVC / MVVM / Separation of layers- best practices?

查看:144
本文介绍了MVC/MVVM/分层中的ViewModels-最佳做法?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对使用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

ViewModel包含域模型的实例是否可以接受?

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


有趣的部分:这不仅限于MVC中的视图模型,实际上是 好的旧数据,业务和ui图层 .我将在稍后说明,但现在;请记住,它不仅适用于MVC,而且还适用于许多其他设计模式.

我将首先指出一些适用的概念,并在以后放大一些实际方案和示例.


让我们考虑不混合各层的优点和缺点.

要花多少钱

总会有一个陷阱,我将它们进行总结,稍后再解释,并说明为什么它们通常不适用

  • 重复代码
  • 增加了额外的复杂性
  • 出色的表现

您将获得的成就

总会有胜利,我将总结一下,稍后再解释,并说明为什么这实际上是有道理的

  • 各层的独立控制

费用


重复代码

不是 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
}

还有更多工作要做!

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

需要更多维护"

是的,这就是为什么您需要对映射进行单元测试(记住,不要重复映射).

增加了额外的复杂性

不,不是.它增加了一个额外的层,使其更加复杂.它不会增加复杂性.

我的一个聪明朋友曾经这样说过:

飞行的飞机是非常复杂的事情.下降的飞机是非常复杂的事情."

他不是唯一使用这种方法的人定义,区别在于可预测性,它与有实际关系,而是对混乱.

通常:模式不会增加复杂性.它们的存在是为了帮助您降低复杂性.它们是众所周知的问题的解决方案.显然,实施不当的模式无济于事,因此您需要在应用模式之前了解问题.忽略这个问题也无济于事.它只是增加了技术债务,必须在某个时候偿还.

添加一层可为您提供明确定义的行为,由于明显的额外映射,该行为将更加(复杂).进行更改时,用于各种目的的混合层将导致无法预料的副作用.重命名数据库列将导致UI中的键/值查找不匹配,从而使您进行不存在的API调用.现在,请考虑一下这以及它与调试工作和维护成本之间的关系.

表现出色

是的,额外的映射将导致消耗更多的CPU能力.但是,与从数据库中获取数据相比,这是微不足道的(除非您将树莓派连接到远程数据库).底线:如果这是一个问题:请使用缓存.

胜利


各层的独立控制

这是什么意思?

此(和更多)的任何组合:

  • 创建可预测的系统
  • 在不影响用户界面的情况下更改业务逻辑
  • 在不影响业务逻辑的情况下更改数据库
  • 在不影响数据库的情况下更改用户界面
  • 能够更改您的实际数据存储
  • 完全独立的功能,孤立的可测试行为且易于维护
  • 应对变化并赋予业务权力

本质上:您可以通过更改定义良好的代码来进行更改,而不必担心讨厌的副作用.

当心:商业对策!

这是为了反映变化,不会改变!"

变革将到来:每年花费数万亿美元 a>不能简单地过去.

很好.但是作为开发人员面对现实;没有犯错的那一天就是您停止工作的那一天.同样适用于业务需求.

有趣的事实;软件熵

我的(微型)服务或工具足够小以应付它!"

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

暂时让它快速简便地完成."

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

关闭以进行修改而打开以进行扩展"呢?这也是SOLID原则!"

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

如果某个模块可供其他模块使用,则将其称为已关闭.假设已为模块提供了定义良好,稳定的描述(就信息隐藏而言,接口是

实际上促进了层的分离.


现在,一些典型情况:

#ASP.NET MVC

既然如此,这就是您在实际问题中所使用的:

让我举个例子.想象一下以下视图模型和域模型:

注释:这也适用于其他图层类型,仅举几例:DTO,DAO,实体,ViewModel,域等.

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;} 
}

因此,在控制器中的某个位置填充 FooViewModel 并将其传递给视图.

现在,请考虑以下情形:

1)域模型改变.

在这种情况下,您可能还需要调整视图,这在分离关注点的情况下是一种不好的做法.

如果您已将ViewModel与DomainModel分开,则只需对映射进行较小的调整(ViewModel => DomainModel(并向后))即可.

2)DomainClass具有嵌套属性,您的视图仅显示"GenreName"

我已经看到在实际情况下这是错误的.

在这种情况下,一个常见的问题是@Html.EditorFor的使用将导致嵌套对象的输入.这可能包括Id和其他敏感信息.这意味着泄漏实施细节!您的实际页面与域模型相关联(可能与域模型相关联).学习完本课程后,您将发现自己创建了hidden输入.如果将其与服务器端模型绑定或自动映射器结合使用,将越来越难以使用Firebug之类的工具来阻止对隐藏的Id的操纵,或者忘记在属性中设置属性,将使其在您的视图中可用. /p>

尽管有可能(也许很容易)阻止其中一些字段,但是您拥有的嵌套的Domain/Data对象越多,正确设置此部分的难度就越大.和;如果您在多个视图中使用"此域模型怎么办?他们会表现得一样吗?另外,请记住,由于不一定要针对视图的原因,您可能想更改DomainModel.因此,对于DomainModel的每一次更改,您都应该意识到,它可能会影响控制器的视图和安全性.

3)在ASP.NET MVC中,通常使用验证属性.

您真的希望您的域包含有关您的视图的元数据吗?还是将view-logic应用于您的数据层?您的视图验证是否始终与域验证相同?它是否具有相同的字段(或其中一些是串联的)?它具有相同的验证逻辑吗?您正在使用域模型跨应用程序吗?等

我认为很显然这不是走的路.

4)更多

我可以为您提供更多方案,但这只是更吸引人的口味问题.我只是希望现在您能明白这一点:)不过,我答应了一个例子:

现在,对于真正的肮脏和快速赢家,它会起作用,但我认为您不应该想要它.

构建视图模型只需要多花些功夫,通常与领域模型相似的比例为80%以上.感觉好像在做不必要的映射,但是当出现第一个概念差异时,您会发现值得付出努力:)

因此,我为一般情况建议以下设置:

  • 创建一个视图模型
  • 创建域模型
  • 创建数据模型
  • 使用类似automapper的库来创建一个到另一个的映射(这将有助于将Foo.FooProp映射到OtherFoo.FooProp)

例如,好处是;如果您在其中一个数据库表中创建一个额外的字段,则不会影响您的视图.它可能会击中您的业务层或映射,但在那里会停止.当然,大多数时候您也想更改视图,但是在这种情况下,您不需要 .因此,它可以将问题隔离在代码的一部分中.

Web API/数据层/DTO

这将在Web-API/ORM(EF)方案中如何工作的另一个具体示例:

这里更直观,尤其是当使用者是第三方时,您的域模型不太可能与您的使用者的实现相匹配,因此视图模型更可能是完全独立的.

注释:名称域模型",也可以称为DTO或模型"

请注意,在Web(或HTTP或REST)API中;通信通常由数据传输对象(DTO)完成,该对象是HTTP端点上公开的实际内容".

因此,您可能会问我们应该将这些DTO放在哪里.它们在领域模型和视图模型之间吗?嗯,是;我们已经看到将它们视为viewmodel将会很困难,因为消费者可能会实现自定义视图.

DTO是否能够替换domainmodels,或者它们有理由独立存在?通常,分隔的概念也将适用于DTO'sdomainmodels.但是再说一遍:你可以问问自己(这就是我有点务实的地方);域中是否有足够的逻辑来明确定义domainlayer?我认为您会发现,如果您的服务变得越来越小,则作为domainmodels一部分的实际logic也将减少,并可能被排除在外,最终您将得出:

EF/(ORM) EntitiesDTOConsumers


免责声明/注释

正如@mrjoltcola所说:还需要记住组件的过度设计.如果以上都不适用,并且可以信任用户/程序员,那您就很好了.但是请记住,由于DomainModel/ViewModel的混合,可维护性和可重用性将降低.

I'm fairly new to the using ViewModels and I wonder, is it acceptable for a ViewModel to contain instances of domain models as properties, or should the properties of those domain models be properties of the ViewModel itself? For example, if I have a class 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; }
}

Would you typically have the ViewModel hold an instance of the Album.cs class, or would you have the ViewModel have properties for each of the Album.cs class' properties.

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?

Basically not because you are literally mixing two layers and tying them together. I must admit, I see it happen a lot and it depends a bit on the quick-win-level of your project, but we can state that it's not conform the Single Responsibility Principle of SOLID.


The fun part: this is not limited to view models in MVC, it's actually a matter of separation of the good old data, business and ui layers. I'll illustrate this later, but for now; keep in mind it applies to MVC, but also, it applies to many more design patterns as well.

I'll start with pointing out some general applicable concepts and zoom in into some actual scenario's and examples later.


Let's consider some pros and cons of not mixing the layers.

What it will cost you

There is always a catch, I'll sum them, explain later, and show why they are usually not applicable

  • duplicate code
  • adds extra complexity
  • extra performance hit

What you'll gain

There is always a win, I'll sum it, explain later, and show why this actually makes sense

  • independent control of the layers

The costs


duplicate code

It's not DRY!

You will need an additional class, which is probably exactly the same as the other one.

This is an invalid argument. The different layers have a well defined different purpose. Therefore, the properties which lives in one layer have a different purpose than a property in the other - even if the properties have the same name!

For example:

This is not repeating yourself:

public class FooViewModel
{
    public string Name {get;set;}
}

public class DomainModel
{
    public string Name {get;set;}
}

On the other hand, defining a mapping twice, is repeating yourself:

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!

Really, is it? If you start coding, more than 99% of the models will overlap. Grabbing a cup of coffee will take more time ;-)

"It needs more maintenance"

Yes it does, that's why you need to unit test your mapping (and remember, don't repeat the mapping).

adds extra complexity

No, it does not. It adds an extra layer, which make it more complicated. It does not add complexity.

A smart friend of mine, once stated it like this:

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

He is not the only one using such a definition, the difference is in predictability which has an actual relation with entropy, a measurement for chaos.

In general: patterns do not add complexity. They exist to help you reduce complexity. They are solutions to well known problems. Obviously, a poorly implemented pattern doesn't help therefore you need to understand the problem before applying the pattern. Ignoring the problem doesn't help either; it just adds technical debt which has to be repaid sometime.

Adding a layer gives you well defined behavior, which due to the obvious extra mapping, will be a (bit) more complicated. Mixing layers for various purposes will lead to unpredictable side-effects when a change is applied. Renaming your database column will result in a mismatch in key/value-lookup in your UI which makes you do a non existing API call. Now, think of this and how this will relate to your debugging efforts and maintenance costs.

extra performance hit

Yes, extra mapping will lead to extra CPU power to be consumed. This, however (unless you have a raspberry pi connected to a remote database) is negligible compared to fetching the data from the database. Bottom line: if this is an issue: use caching.

The win


independent control of the layers

What does this mean?

Any combination of this (and more):

  • creating a predictable system
  • altering your business logic without affecting your UI
  • altering your database, without affecting your business logic
  • altering your ui, without affecting your database
  • able to change your actual data store
  • total independent functionality, isolated well testable behavior and easy to maintain
  • cope with change and empower business

In essence: you are able to make a change, by altering a well defined piece of code without worrying about nasty side effects.

beware: business counter measures!

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

Change will come: spending trillions of US dollar annually cannot simply pass by.

Well that's nice. But face it, as a developer; the day you don't make any mistakes is the day you stop working. Same applies to business requirements.

fun fact; software entropy

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

This might be the toughest one since there is actually a good point here. If you develop something for one time use, it probably is not able to cope with the change at all and you have to rebuild it anyway, provided you are actually going to reuse it. Nevertheless, for all other things: "change will come", so why make the change more complicated? And, please note, probably, leaving out layers in your minimalistic tool or service will usually puts a data layer closer to the (User)Interface. If you are dealing with an API, your implementation will require a version update which needs to be distributed among all your clients. Can you do that during a single coffee break?

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

Is your job "for the time being"? Just kidding ;-) but; when are you going to fix it? Probably when your technical debt forces you to. At that time it cost you more than this short coffee break.

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

Yes, it is! But this doesn't mean you shouldn't fix typo's. Or that every applied business rule can be expressed as an sum of extensions or that you are not allowed to fix things that are broken. Or as Wikipedia states it:

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)

which actually promotes separation of layers.


Now, some typical scenarios:

#ASP.NET MVC

Since, this is what you are using in your actual question:

Let me give an example. Imagine the following view model and domain model:

note: this is also applicable to other layer types, to name a few: DTO, DAO, Entity, ViewModel, Domain, etc.

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;} 
}

So, somewhere in your controller you populate the FooViewModel and pass it on to your view.

Now, consider the following scenarios:

1) The domain model changes.

In this case you'll probably need to adjust the view as well, this is bad practice in context of separation of concerns.

If you have separated the ViewModel from the DomainModel, a minor adjustment in the mappings (ViewModel => DomainModel (and back)) would be sufficient.

2) The DomainClass has nested properties and your view just displays the "GenreName"

I have seen this go wrong in real live scenarios.

In this case a common problem is that the use of @Html.EditorFor will lead to inputs for the nested object. This might include Ids and other sensitive information. This means leaking implementation details! Your actual page is tied to your domain model (which is probably tied to your database somewhere). Following this course, you'll find yourself creating hidden inputs. If you combine this with a server side model binding or automapper it's getting harder to block the manipulation of hidden Id's with tools like firebug, or forgetting to set an attribute on your property, will make it available in your view.

Although it's possible, maybe easy, to block some of those fields, but the more nested Domain/Data objects you have, the more trickier it will become to get this part right. And; what if you are "using" this domainmodel in multiple views? Will they behave the same? Also, bear in mind, that you might want to change your DomainModel for a reason that's not necessarily targeting the view. So with every change in your DomainModel you should be aware that it might affect the view(s) and the security aspects of the controller.

3) In ASP.NET MVC it is common to use validation attributes.

Do you really want your domain to contain metadata about your views? Or apply view-logic to your data-layer? Is your view-validation always the same as the domain-validation? Does it has the same fields (or are some of them a concatenation)? Does it have the same validation logic? Are you are using your domain-models cross application? etc.

I think it's clear this is not the route to take.

4) More

I can give you more scenario's but it's just a matter of taste to what's more appealing. I'll just hope at this point you'll get the point :) Nevertheless, I promised an illustration:

Now, for really dirty and quick-wins it will work, but I don't think you should want it.

It's just a little more effort to build a view-model, which usually is for 80+% similar to the domain model. This might feel like doing unnecessary mappings, but when the first conceptual difference arises, you'll find that it was worth the effort :)

So as an alternative, I propose the following setup for a general case:

  • create a viewmodel
  • create a domainmodel
  • create a datamodel
  • use a library like automapper to create mapping from one to the other (this will help to map Foo.FooProp to OtherFoo.FooProp)

The benefits are, e.g.; if you create an extra field in one of your database tables, it won't affect your view. It might hit your business layer or mappings, but there it will stop. Of course, most of the time you want to change your view as well, but in this case you don't need to. It therefore keeps the problem isolated in one part of your code.

Web API / data-layer / DTO

Another concrete example of how this will work in a Web-API / ORM (EF) scenario:

Here it's more intuitive, especially when the consumer is a third party, it's unlikely your domain model matches the implementation of your consumer, therefore a viewmodel is more likely to be fully self-contained.

note: The name "domain model", can also be referred to as DTO or "Model"

Please note that in Web (or HTTP or REST) API; communications is often done by a data-transfer-object (DTO), which is the actual "thing" that's being exposed on the HTTP-endpoints.

So, where should we put these DTO's you might ask. Are they between domain model and view models? Well, yes; we have already seen that treating them as viewmodel would be hard since the consumer is likely to implement a customized view.

Would the DTO's be able to replace the domainmodels or do they have a reason to exists on their own? In general, the concept of separation would be applicable to the DTO's and domainmodels as well. But then again: you can ask yourself (,and this is where I tend to be a bit pragmatic,); is there enough logic within the domain to explicitly define a domainlayer? I think you'll find that if your service get smaller and smaller, the actual logic, which is part of the domainmodels, decreases as well and may be left out all together and you'll end up with:

EF/(ORM) EntitiesDTOConsumers


disclaimer / note

As @mrjoltcola stated: there is also component over-engineering to keep in mind. If none of the above applies, and the users/programmers can be trusted, you are good to go. But keep in mind that maintainability and re-usability will decrease due to the DomainModel/ViewModel mixing.

这篇关于MVC/MVVM/分层中的ViewModels-最佳做法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆