实体框架6重用数据注释
本文关键字:数据 注释 6重 框架 实体 | 更新日期: 2023-09-27 18:01:09
我已经寻找了一段时间的最终解决方案,但尚未得出结论。我想在数据模型类上只指定一次数据注释,并让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; }
}
现在,我想指定一个视图模型,它包含一个密码确认字段,该字段不会存储在数据库和其他可能的数据模型中,但我不想再次指定所有数据注释属性。这是关于尝试找到最佳实践,但也需要维护多个声明,在某些情况下,其中一个声明会被修改,而其他声明则不会。
我看过一个接口/继承解决方案,但由于各种原因,它并没有完全解决问题。另一个潜在的解决方案可能是在视图模型类(或属性)上设置一个Attribute,表示从…继承属性。。。但到目前为止我还找不到合适的东西。
有人有什么聪明的想法或以合适的方式实现了这一点吗?
您可以用以下内容装饰您的视图模型类:
[MetadataType(typeof(YourModelClass))]
public class YourViewModelClass
{
// ...
}
假设两个类都具有相同的属性,则注释将正确地嵌入其中。
有关详细信息,请参阅MSDN中的MetadataTypeAttribute类。
注意:在MDSN注释中,它们解释了为现有模型类添加额外元数据的用途,但这将适用于您的情况。事实上,您可以一直这样做:对ViewModel进行注释,并将其应用于模型的分部类。或者甚至创建一个应用于模型中实体和视图模型的好友类。有了这些选项中的任何一个,任何类都可以"定义"自己的注释,并从buddy类"继承"其他注释。
重要提示:该解决方案与Hoots接受的解决方案非常不同,因为该解决方案被EF或MVC等框架识别,用于创建模型或提供自动数据验证。Hoots的解决方案必须手动使用,因为框架不会自动使用它。
我看过一个接口/继承解决方案,但由于各种原因,它并没有完全解决问题
你能告诉我们为什么遗产对你不起作用吗?
以下是继承解决方案的示例:
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。这些家伙就像模型,但它们不代表域,也不持久化。它们代表UI。
有关ViewModels 的更多信息
- https://stackoverflow.com/a/11074506/1027250
- https://stackoverflow.com/a/14468362/1027250
基于前面的示例,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
我可能想在同一视图中组合不同的数据模型
这正是ViewModels的用途。您可以专门为视图创建它们。
你要求的是互斥的东西。。。您希望继承元数据,但不希望以任意方式继承元数据。。。而这通常不是一个有用的场景。与维护单独的模型相比,你将花费更多的时间来尝试找到一些神奇的场景,而且你很可能最终会得到一个你不是100%满意的低于标准的解决方案。
我知道你的沮丧。。你不应该在多个地方维护相同的数据。。你违反了DRY原则。。不要重复你自己。。。
问题是,你创建的任何解决方案都将违反其他原则,例如开放-封闭原则或单一责任原则。通常情况下,这些原则彼此不一致,你必须在某个地方找到平衡。
简单地说,最佳实践是使用单独的数据属性维护单独的视图和域模型,因为您的域和视图是单独的关注点,它们应该保持独立。如果他们没有单独的顾虑,你可以简单地对两者使用相同的模型,就没有必要跳过这些障碍。
视图模型是分开的,正是因为您的视图通常与您的域有不同的要求。把两者混为一谈只会带来痛苦。
这是我在考虑了其他可能的解决方案后提出的一个解决方案。这是基于找到以下文章。。。
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")]
例如
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);
它将首先直接查看视图模型的属性属性,但如果没有找到任何属性,它将通过调用GetInheritedAttributeOfType来查看继承的属性。
这对我来说最有效,因为。。。
我觉得当前在视图模型和数据模型中重复DataAnnotations的做法对于可维护性或重用性来说并不好。
使用MetadataType也是不灵活的,要么全有要么全无,并且不能在单个ViewModel上包含多个MetadataType属性。
在没有Properties的视图模型中封装数据模型是缺乏的,因为它也不具有灵活性。您必须包括整个封装的对象,这样就不能在多个视图中填充DataModel。