MVC / MVVM中的ViewModels /分层 – 最佳实践?
我对使用ViewModel相当新,我想知道,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 Genres { get; set; } public IEnumerable 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 Genres { get; set; } public IEnumerable Artists { get; set; } public int Rating { get; set; } // other properties specific to the View }
有趣的部分:这不仅限于MVC
viewmodels
,它实际上是分离“好的旧数据/业务/ ui层”的问题,即关注点的分离。 我稍后会说明这一点,但是现在; 请记住,它也适用于MVVM
或任何其他设计模式。
ViewModel是否可以包含域模型的实例?
基本上没有,虽然我看到它发生了很多。 这取决于项目的quick-win
水平。
让我举个例子。 想象一下以下视图模型:
public class FooViewModel { public string Name {get; set;} public DomainClass Genre {get;set;} }
和以下DomainClass
//also applies to database data/POCO classes public class DomainClass { public int Id {get; set;} public string Name {get;set;} }
因此,在控制器的某个位置填充FooViewModel并将其传递给您的视图。
现在,请考虑以下方案:
1)域模型发生变化。
在这种情况下,您可能还需要调整视图,这在关注点分离的背景下是不好的做法。
如果已将ViewModel与DomainModel分开,则对映射(ViewModel => DomainModel(和back))进行微调就足够了。
2)DomainClass具有嵌套属性,您的视图只显示GenreName
。
我在实际场景中看到过这个问题。
在这种情况下,常见的问题是使用@Html.EdittorFor
将导致嵌套对象的输入。 这可能包括Id
和其他敏感信息。 按照本课程,您将发现自己创建hidden
输入。 如果你将它与服务器端模型绑定或者自动化器结合起来,那么使用像firebug这样的工具来阻止隐藏Id
的操作真的很难。
虽然阻止其中一些字段是可能的,也许很容易,但是你拥有的嵌套域/数据对象越多,保护这部分就越困难。 请记住,您可能希望更改您的DomainModel,原因不一定是针对视图。 因此,对于DomainModel中的每个更改,您应该意识到它可能会影响控制器的视图和安全性方面。
3)在asp.net-MVC中,通常使用validation属性。
您真的希望您的域包含有关您的观点的元数据吗? 或者将视图逻辑应用于数据层? 您的视图validation是否始终与域validation相同? 它是否具有相同的validation逻辑? 您使用的是域模型交叉应用程序吗? 等等
我认为这显然不是采取的路线。
4)更多
我可以给你更多的场景,但这只是一个品味更具吸引力的问题。 我只希望在这一点上你能得到点:)尽管如此,我答应了一个例子:
现在,对于非常肮脏和quick-wins
它会起作用,但我认为你不应该想要它。
构建视图模型只需要多一点努力,通常与域模型类似,为80 +%。 这可能感觉就像做了不必要的映射,但是当出现第一个概念差异时,你会发现它值得努力:)
因此,作为替代方案,我建议对一般情况进行以下设置:
- 创建一个viewmodel
- 创建一个域名模型
- 创建一个数据模型
- 使用像
automapper
这样的库来创建从一个到另一个的映射(这将有助于将Foo.FooProp
映射到OtherFoo.FooProp
)
好处是,例如; 如果在其中一个数据库表中创建一个额外字段,则不会影响您的视图。 它可能会触及您的业务层或映射,但它会停止。 当然,大多数时候你也想改变你的观点,但在这种情况下你不需要 。 因此,它将问题隔离在代码的一部分中。
web api / data-layer
另一个具体示例说明了它在Web-API / EF场景中的工作原理:
注意
正如@mrjoltcola所说:还有一个过度引擎的组件要记住。 如果上述情况不适用,并且用户/程序员可以信任,那么你的好处就是去。 但请记住,由于DomainModel / ViewModel混合,可维护性和可重用性会降低。
从技术最佳实践和个人偏好的混合看,意见各不相同。
在视图模型中使用域对象,甚至使用域对象作为模型都没有错 ,而且很多人都这样做。 有些人强烈建议为每个视图创建视图模型,但就个人而言,我觉得许多应用程序都是由开发人员过度设计的,他们学习并重复他们习以为常的方法。 事实上,有几种方法可以使用较新版本的ASP.NET MVC来实现目标。
当您为视图模型以及业务和持久层使用通用域类时,最大的风险是模型注入。 向模型类添加新属性可以将这些属性公开在服务器边界之外。 攻击者可能会看到他不应该看到的属性(序列化)并改变他不应该改变的值(模型绑定器)。
为防止注射,请使用与您的整体方法相关的安全实践。 如果您计划使用域对象,请确保在控制器中或通过模型绑定器注释使用白名单或黑名单(包含/排除)。 黑名单更方便,但是编写未来修订版的懒惰开发人员可能会忘记它们或者不了解它们。 白名单([Bind(Include = …)]是强制性的,在添加新字段时需要注意,因此它们充当内联视图模型。
例:
[Bind(Exclude="CompanyId,TenantId")] public class CustomerModel { public int Id { get; set; } public int CompanyId { get; set; } // user cannot inject public int TenantId { get; set; } // .. public string Name { get; set; } public string Phone { get; set; } // ... }
要么
public ActionResult Edit([Bind(Include = "Id,Name,Phone")] CustomerModel customer) { // ... }
第一个示例是在整个应用程序中强制执行多租户安全性的好方法。 第二个示例允许自定义每个操作。
在您的方法中保持一致,并清楚地记录项目中用于其他开发人员的方法。
我建议您始终使用视图模型进行登录/配置文件相关function,以强制自己“编组”Web控制器和数据访问层之间的字段作为安全练习。