MVC - 验证属性,包含来自数据库

时间:2016-10-20 10:11:13

标签: asp.net-mvc localization custom-attributes

我需要将我的翻译保留在数据库中,以便用户可以添加,删除和更改它们。我将所有翻译都保存在一个包含复合主键(variableName,culture)的表中,其中variableName只是一些可以有多个翻译的文本的名称,它们对应于一个字符串的文化,例如" en -US&#34 ;.例如,我有一个变量" submitLogin"我在登录按钮上显示,我的数据库中有三种语言:英语,德语和波兰语。这意味着我在表格中为该特定文本保留了三行:(" submitLogin"," en-US","英文翻译),(" submitLogin&# 34;," de-DE","德语翻译")和(" submitLogin"," pl-PL","波兰语翻译")。

到目前为止,我的应用程序基于一个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,因为它们是在运行一个控制台程序后生成的,该程序从数据库中读取转换变量的所有唯一值并创建属性(如上所示)。我不能再拥有此文件,因为用户可以在运行时添加新的翻译变量。

如何尽可能少地更改并使这些属性从数据库中读取错误消息,显示名称等?

我有三个想法,但我不知道如何完成它们:

  1. 使用自定义属性 - 我尝试将其用于必需属性,但它不会在HTML中添加任何客户端验证或任何错误消息。

  2. 使用自定义DataAnnotationsModelMetadataProvider - 我试过这个但是在重新加载页面后它不起作用 - 在第一页上加载所有错误(特别是:'现场电子邮件是必需的。&#39 ;)但是在重新加载页面后,需要将错误消息更改为'字段是必需的'。这就是我的工作:

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

    }

  3. 的Global.asax:

    ModelMetadataProviders.Current = new CustomDataAnnotationsProvider();
    

    型号:

    [Required(ErrorMessage = "errorRequired")]
    [EmailAddress(ErrorMessage = "errorWrongDataType")]
    [Display(Name = "nameEmail")]
    public string Email { get; set; }
    
    1. 使用反射(会是最好的,但我不知道怎么做)。让我们说我保留我的属性,并从我的Resources.cs中删除所有属性。现在RequiredAttribute做了什么?它采用给定的类型并获得给定的属性,例如,它试图这样做:

      Resources.Resources.nameEmail.get
      
    2. 问题是:是否可以编写一些反映代码来处理&#39;请求&#39;对于不存在的属性(如nameEmail)?

1 个答案:

答案 0 :(得分:2)

我认为答案是在Resources.cs file中提供默认值。虽然用户可以动态提供翻译,但是除非他们拥有您在模型中使用过的密钥,否则您的应用程序无法使用它们。

如果您修改现有方法以接受默认值,那么如果没有数据库值,您可以返回此值:

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("'", "''"));
    }