MVC - 带有来自数据库的错误消息的验证属性
MVC - validation attributes with error messages from database
我需要将我的翻译保存在数据库中,以便用户可以添加、删除和更改它们。我将我所有的翻译保存在一个 table 中,并带有复合主键(variableName,culture),其中 variableName 只是一些可以有多种翻译的文本的名称,它们对应于字符串的文化,例如 "en-US"。例如,我有一个显示在登录按钮上的变量 "submitLogin",我的数据库中有三种语言:英语、德语和波兰语。这意味着我在 table 中为该特定文本保留了三行:("submitLogin", "en-US", "英文翻译", ("submitLogin", "de-DE", "German translation") 和 ("submitLogin", "pl-PL", "Polish translation").
到目前为止,我的应用程序一直基于 class Resources.cs,其中包含来自数据库的所有翻译变量,例如:
public static string buttonContinueShopping {
get {
return (string) resourceProvider.GetResource("buttonContinueShopping", CultureInfo.CurrentUICulture.Name);
}
}
在视图中,我使用这些静态属性来获得这样的翻译:
@Resources.buttonContinueShopping
我可以创建一个在视图中表现完全相同的动态类型(除了没有静态属性,但我可以在每个视图上创建一个对象,这不是问题 - 虽然它看起来不太好):
public class Resource : DynamicObject
{
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
ResourceManager rsManager = new ResourceManager();
result = rsManager.GetString(binder.Name);
return true;
}
}
但是我的模型属性有问题。到目前为止,我是这样使用它们的:
[Required(ErrorMessageResourceType = typeof(Resources.Resources), ErrorMessageResourceName = "errorRequired")]
[DataType(DataType.EmailAddress, ErrorMessageResourceType = typeof(Resources.Resources), ErrorMessageResourceName = "errorWrongDataType")]
[EmailAddress]
[Display(Name = "nameEmail", ResourceType = typeof(Resources.Resources))]
public string Email { get; set; }
现在我必须摆脱我的 Resources.cs 因为它们是在 运行 一个控制台程序之后生成的,该程序从数据库中读取翻译变量的所有唯一值并创建属性(就像我展示的那样多于)。我不能再拥有这个文件了,因为用户可以在运行时添加新的翻译变量。
如何尽可能少地更改并使这些属性从数据库中读取错误消息、显示名称等?
我有三个想法,但我不知道如何实现它们:
使用自定义属性 - 我尝试将其用于 Required 属性,但它没有添加任何客户端验证,也没有在 HTML.
[= 中添加任何错误消息54=]
使用自定义 DataAnnotationsModelMetadataProvider - 我试过了但是在重新加载页面后它不起作用 - 在第一页加载时存在所有错误(特别是这个:'Field Email is required.')但是在重新加载之后页面,所需的错误消息更改为 'Field is required'。这就是我所做的:
public class CustomDataAnnotationsProvider: DataAnnotationsModelMetadataProvider
{
private ResourceManager resourceManager = new ResourceManager();
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
{
string key = string.Empty;
string localizedValue = string.Empty;
foreach (var attr in attributes)
{
if (attr != null)
{
if (attr is DisplayAttribute)
{
key = ((DisplayAttribute)attr).Name;
if (!string.IsNullOrEmpty(key) && !key.Contains(" "))
{
localizedValue = resourceManager.GetString(key);
((DisplayAttribute)attr).Name = localizedValue;
}
}
else if (attr is ValidationAttribute || attr is RequiredAttribute)
{
key = ((ValidationAttribute)attr).ErrorMessage;
if (!string.IsNullOrEmpty(key) && !key.Contains(" "))
{
localizedValue = resourceManager.GetString(key);
((ValidationAttribute)attr).ErrorMessage = localizedValue;
}
}
}
}
return base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
}
Global.asax:
ModelMetadataProviders.Current = new CustomDataAnnotationsProvider();
型号:
[Required(ErrorMessage = "errorRequired")]
[EmailAddress(ErrorMessage = "errorWrongDataType")]
[Display(Name = "nameEmail")]
public string Email { get; set; }
使用反射(最好,但我不知道该怎么做)。假设我保留我的属性,并从我的 Resources.cs 中删除所有属性。现在 RequiredAttribute 做了什么?它采用给定的类型并获得给定的 属性,例如它试图这样做:
Resources.Resources.nameEmail.get
问题是:是否可以编写一些反射代码来处理 'requests' 不存在的属性(如 nameEmail
)?
我认为答案是在 Resources.cs file
中提供默认值。虽然用户可以动态提供翻译,但您的应用程序无法使用它们,除非他们拥有您在模型中使用的密钥。
如果您修改现有方法以接受默认值,那么您可以 return 如果不存在数据库值:
public static string buttonContinueShopping {
get {
return GetResource("buttonContinueShopping",
CultureInfo.CurrentUICulture.Name, "Continue Shopping");
}
}
public string GetResource(string key, string cultureName, string defaultText)
{
// Get db value
if (dbValue != null)
return dbValue;
return defaultText;
}
您可以通过在 Resources.cs
中手动修改密钥来控制密钥,哪个 IMO 是它们的最佳位置,因为它们在使用时在同一个项目中维护。您可以(我已经使用过这种技术)然后编写一个配套的控制台应用程序,该应用程序可以使用反射来生成更新数据库所需的 sql。
这个例子直接取自我的项目,但你可以理解这个想法
static void Main(string[] args)
{
var resources = typeof(Resources).GetProperties();
StreamWriter streamWriter = new StreamWriter(new FileStream(@"..\..\Resources.sql", FileMode.Create));
streamWriter.WriteLine(createTable);
for (int i = 0; i < resources.Count(); i++)
{
var line = GetValues(resources[i].Name, resources[i].GetValue(null, null) as string);
if (i == 0)
{
streamWriter.Write(insertValues + line);
}
else if (i == resources.Count() - 1)
{
streamWriter.Write(",\r\n" + line + "\r\nGO");
}
else if (i % 4 == 0)
{
streamWriter.Write("\r\nGO\r\n\r\n" + insertValues + line);
}
else
{
streamWriter.Write(",\r\n" + line);
}
}
streamWriter.Flush();
streamWriter.Close();
}
private static string createTable = "IF NOT EXISTS (SELECT * FROM sys.objects where Object_Id = OBJECT_ID(tempdb..#Resources))"
+ "\r\n\tCREATE TABLE Resources (StaticTextKey VARCHAR(100), DefaultText VARCHAR(MAX))\r\nGO\r\n";
private static string insertValues = "INSERT INTO #Resources (StaticTextKey, DefaultText) VALUES\r\n";
private static string GetValues(string staticTextKey, string defaultText)
{
return string.Format("('{0}', '{1}')", staticTextKey, defaultText.Replace("'", "''"));
}
我需要将我的翻译保存在数据库中,以便用户可以添加、删除和更改它们。我将我所有的翻译保存在一个 table 中,并带有复合主键(variableName,culture),其中 variableName 只是一些可以有多种翻译的文本的名称,它们对应于字符串的文化,例如 "en-US"。例如,我有一个显示在登录按钮上的变量 "submitLogin",我的数据库中有三种语言:英语、德语和波兰语。这意味着我在 table 中为该特定文本保留了三行:("submitLogin", "en-US", "英文翻译", ("submitLogin", "de-DE", "German translation") 和 ("submitLogin", "pl-PL", "Polish translation").
到目前为止,我的应用程序一直基于 class Resources.cs,其中包含来自数据库的所有翻译变量,例如:
public static string buttonContinueShopping {
get {
return (string) resourceProvider.GetResource("buttonContinueShopping", CultureInfo.CurrentUICulture.Name);
}
}
在视图中,我使用这些静态属性来获得这样的翻译:
@Resources.buttonContinueShopping
我可以创建一个在视图中表现完全相同的动态类型(除了没有静态属性,但我可以在每个视图上创建一个对象,这不是问题 - 虽然它看起来不太好):
public class Resource : DynamicObject
{
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
ResourceManager rsManager = new ResourceManager();
result = rsManager.GetString(binder.Name);
return true;
}
}
但是我的模型属性有问题。到目前为止,我是这样使用它们的:
[Required(ErrorMessageResourceType = typeof(Resources.Resources), ErrorMessageResourceName = "errorRequired")]
[DataType(DataType.EmailAddress, ErrorMessageResourceType = typeof(Resources.Resources), ErrorMessageResourceName = "errorWrongDataType")]
[EmailAddress]
[Display(Name = "nameEmail", ResourceType = typeof(Resources.Resources))]
public string Email { get; set; }
现在我必须摆脱我的 Resources.cs 因为它们是在 运行 一个控制台程序之后生成的,该程序从数据库中读取翻译变量的所有唯一值并创建属性(就像我展示的那样多于)。我不能再拥有这个文件了,因为用户可以在运行时添加新的翻译变量。
如何尽可能少地更改并使这些属性从数据库中读取错误消息、显示名称等?
我有三个想法,但我不知道如何实现它们:
使用自定义属性 - 我尝试将其用于 Required 属性,但它没有添加任何客户端验证,也没有在 HTML.
[= 中添加任何错误消息54=]使用自定义 DataAnnotationsModelMetadataProvider - 我试过了但是在重新加载页面后它不起作用 - 在第一页加载时存在所有错误(特别是这个:'Field Email is required.')但是在重新加载之后页面,所需的错误消息更改为 'Field is required'。这就是我所做的:
public class CustomDataAnnotationsProvider: DataAnnotationsModelMetadataProvider { private ResourceManager resourceManager = new ResourceManager(); protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName) { string key = string.Empty; string localizedValue = string.Empty; foreach (var attr in attributes) { if (attr != null) { if (attr is DisplayAttribute) { key = ((DisplayAttribute)attr).Name; if (!string.IsNullOrEmpty(key) && !key.Contains(" ")) { localizedValue = resourceManager.GetString(key); ((DisplayAttribute)attr).Name = localizedValue; } } else if (attr is ValidationAttribute || attr is RequiredAttribute) { key = ((ValidationAttribute)attr).ErrorMessage; if (!string.IsNullOrEmpty(key) && !key.Contains(" ")) { localizedValue = resourceManager.GetString(key); ((ValidationAttribute)attr).ErrorMessage = localizedValue; } } } } return base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
}
Global.asax:
ModelMetadataProviders.Current = new CustomDataAnnotationsProvider();
型号:
[Required(ErrorMessage = "errorRequired")]
[EmailAddress(ErrorMessage = "errorWrongDataType")]
[Display(Name = "nameEmail")]
public string Email { get; set; }
使用反射(最好,但我不知道该怎么做)。假设我保留我的属性,并从我的 Resources.cs 中删除所有属性。现在 RequiredAttribute 做了什么?它采用给定的类型并获得给定的 属性,例如它试图这样做:
Resources.Resources.nameEmail.get
问题是:是否可以编写一些反射代码来处理 'requests' 不存在的属性(如 nameEmail
)?
我认为答案是在 Resources.cs file
中提供默认值。虽然用户可以动态提供翻译,但您的应用程序无法使用它们,除非他们拥有您在模型中使用的密钥。
如果您修改现有方法以接受默认值,那么您可以 return 如果不存在数据库值:
public static string buttonContinueShopping {
get {
return GetResource("buttonContinueShopping",
CultureInfo.CurrentUICulture.Name, "Continue Shopping");
}
}
public string GetResource(string key, string cultureName, string defaultText)
{
// Get db value
if (dbValue != null)
return dbValue;
return defaultText;
}
您可以通过在 Resources.cs
中手动修改密钥来控制密钥,哪个 IMO 是它们的最佳位置,因为它们在使用时在同一个项目中维护。您可以(我已经使用过这种技术)然后编写一个配套的控制台应用程序,该应用程序可以使用反射来生成更新数据库所需的 sql。
这个例子直接取自我的项目,但你可以理解这个想法
static void Main(string[] args)
{
var resources = typeof(Resources).GetProperties();
StreamWriter streamWriter = new StreamWriter(new FileStream(@"..\..\Resources.sql", FileMode.Create));
streamWriter.WriteLine(createTable);
for (int i = 0; i < resources.Count(); i++)
{
var line = GetValues(resources[i].Name, resources[i].GetValue(null, null) as string);
if (i == 0)
{
streamWriter.Write(insertValues + line);
}
else if (i == resources.Count() - 1)
{
streamWriter.Write(",\r\n" + line + "\r\nGO");
}
else if (i % 4 == 0)
{
streamWriter.Write("\r\nGO\r\n\r\n" + insertValues + line);
}
else
{
streamWriter.Write(",\r\n" + line);
}
}
streamWriter.Flush();
streamWriter.Close();
}
private static string createTable = "IF NOT EXISTS (SELECT * FROM sys.objects where Object_Id = OBJECT_ID(tempdb..#Resources))"
+ "\r\n\tCREATE TABLE Resources (StaticTextKey VARCHAR(100), DefaultText VARCHAR(MAX))\r\nGO\r\n";
private static string insertValues = "INSERT INTO #Resources (StaticTextKey, DefaultText) VALUES\r\n";
private static string GetValues(string staticTextKey, string defaultText)
{
return string.Format("('{0}', '{1}')", staticTextKey, defaultText.Replace("'", "''"));
}