我已经找了一段时间才能找到明确的解决方案,但还没有得出结论。我想在数据模型类上只指定一次数据注释,并且UI可以从视图模型类中看到它们,而无需再次指定它们。为了说明我的观点,假设我有一个UserAccount类......
public class UserAccount
{
[Display(Name = "Username", Prompt = "Login As"), Required()]
string UserName { get; set; }
[Display(Name = "Password", Prompt = "Password"), Required(), DataType(DataType.Password), StringLength(255, MinimumLength = 7, ErrorMessage = "The password must be at least 7 characters")]
string Password { get; set; }
}
现在我想指定一个视图模型,其中包含一个密码确认字段,该字段不会存储在数据库中,也可能存储在其他数据模型中,但我不想再次指定所有数据注释属性。这是关于尝试找到最佳实践,但也需要维护多个声明,在某些时候,一个将被修改,而其他声明将不会。
我已经查看了一个接口/继承解决方案,但由于各种原因,它并没有完全切断它。另一个可能的解决方案可能是在View Model类(或属性)上有一个Attribute来表示从...继承属性但是我找不到任何适合它的东西。
有没有人有任何明智的想法或以合适的方式实施这个想法?
答案 0 :(得分:4)
您可以使用此atttribute来装饰您的viewmodel类:
[MetadataType(typeof(YourModelClass))]
public class YourViewModelClass
{
// ...
}
假设两个类具有相同的属性,注释将被正确地包含在内。
有关详细信息,请参阅:MetadataTypeAttribute Class in MSDN。
注意:在MDSN备注中,他们解释了为现有模型类添加额外元数据的用法,但这适用于您的案例。实际上,您可以一直这样做:注释您的ViewModel,并将其应用于模型的部分类。甚至可以创建一个单独的伙伴类,它应用于模型中的实体和视图模型。有了这些选项,任何一个类都可以定义"它自己的注释和"继承"来自哥们班的其他人。
重要提示:此解决方案与Hoots接受的解决方案非常不同,因为此解决方案可被EF或MVC等框架识别,用于创建模型或提供自动数据验证。必须手动使用Hoots的解决方案,因为框架不会自动使用它。
答案 1 :(得分:2)
我已经查看了一个接口/继承解决方案,但由于各种原因,它并没有完全切断它。
你能告诉我们为什么继承不适合你吗?
以下是继承解决方案的示例:
public class UserAccount : PasswordModel
{
[Display(Name = "Username", Prompt = "Login As"), Required()]
public string UserName { get; set; }
[Display(Name = "Password", Prompt = "Password")]
[StringLength(255, MinimumLength = 7, ErrorMessage = "The password must be at least 7 characters")]
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
}
public class ResetPasswordViewModel : PasswordModel
{
[Display(Name = "Retype Password", Prompt = "Password")]
[Required]
[DataType(DataType.Password)]
[CompareAttribute("Password", ErrorMessage = "Password does not match")]
string RetypePassword { get; set; }
}
从M-V-C,您可以将设计模式扩展为M- VM -V-C
ViewModel
VM代表ViewModels。这些人就像模特,但他们不代表域而且他们没有坚持。它们代表了用户界面。
有关ViewModels的更多信息
基于前面的示例,ResetPasswordViewModel
是表示UI的ViewModel。如果您担心持续RetypePassword
,请不要在EF中添加ViewModel。
另一种方法
您可以使用合成替换继承。请参阅下面的示例。
public class ResetPasswordViewModel
{
public UserAccount UserAccount { get; set; }
[Display(Name = "Retype Password", Prompt = "Password")]
[Required]
[DataType(DataType.Password)]
public string RetypePassword { get; set; }
}
使用上面的ViewModel,您可以获得Password
属性的可重用性。您可以像访问它一样访问它:
@Model.UserAccount.Password
@Model.ResetPassword
我可能想在同一视图中组合不同的数据模型
这正是ViewModel的用途。您可以专门为View创建它们。
答案 2 :(得分:1)
您要求互相排斥的东西......您希望继承您的元数据,但您不希望以任意方式继承您的元数据......这通常不是一个有用的场景。你将花费更多的时间来寻找一些神奇的子弹场景,而不是花费你维持单独的模型,你可能仍然会得到一个你不是百分之百满意的低于标准的解决方案。
我知道你的挫败感......你不应该在多个地方保持相同的数据......你违反了DRY原则..不要重复自己......
问题是,您创建的任何解决方案都将违反其他原则,例如开放式原则或单一责任原则。通常这些原则彼此不一致,你必须在某个地方找到平衡点。
简单地说,最佳做法是使用单独的数据属性维护单独的视图和域模型,因为您的域和视图是单独的问题,应该保持独立。如果他们没有单独的顾虑,你可以简单地为两者使用相同的模型,并且没有必要跳过这些箍。
视图模型之间的区别正是因为您的视图通常与您的域有不同的要求。试图将两者混合在一起会导致痛苦。
答案 3 :(得分:1)
这是我在考虑其他可能的解决方案后提出的解决方案。它基于找到以下文章...
http://jendaperl.blogspot.co.uk/2010/10/attributeproviderattribute-rendered.html
我在这篇文章的最后部分列出了我的理由。
拥有现有的数据模型类......
public class UserAccount
{
[Display(Name = "Username", Prompt = "Login As"), Required()]
string UserName { get; set; }
[Display(Name = "Password", Prompt = "Password"), Required(), DataType(DataType.Password), StringLength(255, MinimumLength = 7, ErrorMessage = "The password must be at least 7 characters")]
string Password { get; set; }
}
如果我们可以将属性属性复制到使用它的视图中,那就太棒了,如果我们需要,可以覆盖它们,以便不需要重新审视使用此类的视图在一个简单的属性更改。这是我的解决方案。创建一个新属性,如下所示......
using System.ComponentModel;
namespace MyApp.ViewModels
{
public class AttributesFromAttribute : AttributeProviderAttribute
{
public AttributesFromAttribute(Type type, string property)
: base(type.AssemblyQualifiedName, property)
{
}
public T GetInheritedAttributeOfType<T>() where T : System.Attribute
{
Dictionary<string,object> attrs = Type.GetType(this.TypeName).GetProperty(this.PropertyName).GetCustomAttributes(true).ToDictionary(a => a.GetType().Name, a => a);
return attrs.Values.OfType<T>().FirstOrDefault();
}
}
}
现在您可以将以下内容添加到视图模型类中的相关属性...
[AttributesFrom(typeof(MyApp.DataModel.UserAccount), "UserName")]
... e.g
public class RegisterViewModel
{
public UserAccount UserAccount { get; set; }
public RegisterViewModel()
{
UserAccount = new UserAccount();
}
[AttributesFrom(typeof(MyApp.DataModel.UserAccount), "UserName")]
string UserName { get; set; }
[AttributesFrom(typeof(MyApp.DataModel.UserAccount), "Password")]
string Password { get; set; }
[AttributesFrom(typeof(MyApp.DataModel.UserAccount), "Password")]
[Display(Name = "Confirm Password", Prompt = "Confirm Password"), Compare("Password", ErrorMessage = "Your confirmation doesn't match.")]
public string PasswordConfirmation { get; set; }
}
这样就可以复制可以覆盖的属性(如上面的PasswordConfirmation),允许在同一个视图模型中使用多个数据模型,如果需要从代码访问继承的属性,可以使用GetInheritedAttributeOfType方法。例如......
public static class AttrHelper
{
public static T GetAttributeOfType<T>(this ViewDataDictionary viewData) where T : System.Attribute
{
var metadata = viewData.ModelMetadata;
var prop = metadata.ContainerType.GetProperty(metadata.PropertyName);
var attrs = prop.GetCustomAttributes(false);
// Try and get the attribute directly from the property.
T ret = attrs.OfType<T>().FirstOrDefault();
// If there isn't one, look at inherited attribute info if there is any.
if(ret == default(T))
{
AttributesFromAttribute inheritedAttributes = attrs.OfType<AttributesFromAttribute>().FirstOrDefault();
if (inheritedAttributes != null)
{
ret = inheritedAttributes.GetInheritedAttributeOfType<T>();
}
}
// return what we've found.
return ret;
}
}
这可以从编辑模板中调用,例如......
var dataTypeAttr = AttrHelper.GetAttributeOfType<DataTypeAttribute>(ViewData);
首先会直接查看viewmodel的属性属性,但如果找不到任何内容,它会查看继承的属性,并调用GetInheritedAttributeOfType。
这最适合我,因为......
我觉得目前在视图模型和数据模型中重复DataAnnotations的做法对于可维护性或重用并不是很好。
使用MetadataType也不够灵活,它全有或全无,您无法在单个ViewModel上包含多个MetadataType属性。
缺少在没有属性的视图模型中封装数据模型,因为它也没有灵活性。您必须包含整个封装对象,因此无法在多个视图上填充DataModel。