如何从资源文件中获取C#属性文本?

34

我有一个属性,想从资源文件中加载文本到该属性中。

[IntegerValidation(1, 70, ErrorMessage = Data.Messages.Speed)]
private int i_Speed;

但我一直收到以下错误信息:
"属性参数必须是常量表达式、typeof 表达式或数组创建表达式的属性参数类型"

如果我添加一个字符串而不是Data.Messages.Text,它就可以完美地工作了,例如:

[IntegerValidation(1, 70, ErrorMessage = "Invalid max speed")]

有什么想法吗?


以下是从资源文件中获取数据的步骤: 1 - 添加资源文件,例如命名为Captions.resx。 2 - 添加一些字符串及其值,如FirstName。 3 - <b>注意:</b>您可能会收到错误消息“无法检索属性'FirstName',因为本地化失败。类型“Xxxx.Captions”不是公共的,或者不包含名称为“FirstName”的公共静态字符串属性。”这是因为默认情况下,资源文件属性的访问修饰符设置为internal。在资源文件工具栏中的访问修饰符下拉菜单中将其更改为Public。 4 - 这是代码。 - Asad Naeem
4 - 这是代码 [Required(ErrorMessageResourceName = "RequiredFirstName", ErrorMessageResourceType = typeof(Resources.Captions))] //[Required(ErrorMessage = "Please Enter System Name")] [Display(Name = "FirstName", ResourceType = typeof(Resources.Captions))] public string SystemName { get; set; } - Asad Naeem
9个回答

30

这是我的解决方案。我已经像microsoft在DataAnnotations中所做的那样,添加了resourceName和resourceType属性到attribute中。

public class CustomAttribute : Attribute
{

    public CustomAttribute(Type resourceType, string resourceName)
    {
            Message = ResourceHelper.GetResourceLookup(resourceType, resourceName);
    }

    public string Message { get; set; }
}

public class ResourceHelper
{
    public static  string GetResourceLookup(Type resourceType, string resourceName)
    {
        if ((resourceType != null) && (resourceName != null))
        {
            PropertyInfo property = resourceType.GetProperty(resourceName, BindingFlags.Public | BindingFlags.Static);
            if (property == null)
            {
                throw new InvalidOperationException(string.Format("Resource Type Does Not Have Property"));
            }
            if (property.PropertyType != typeof(string))
            {
                throw new InvalidOperationException(string.Format("Resource Property is Not String Type"));
            }
            return (string)property.GetValue(null, null);
        }
        return null; 
    }
}

1
我将这个方法泛化并在绑定列表中添加了NonPublic,这样我就可以从其他dll中提取资源,而且它运行得很好。实际上,我正在使用它来提取图像资源。 - Peter
@Peter - 你能发一下你修改后的解决方案吗? - Andrew
@Andrew - 我正在一个开源项目中使用这个。你需要挖掘一下,但这是属性类的链接:http://pulse.codeplex.com/SourceControl/changeset/view/71556#1475612 - Peter
@Andrew - 我继续将其发布为单独的答案。 - Peter

26

当你编译时,属性值被硬编码到程序集中。如果你想在执行期间进行任何操作,你需要将常量作为,然后将一些代码放入属性类本身以加载资源。


10
这是我修改过的版本,与我之前组合的版本有所不同:
[System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = false)]
public class ProviderIconAttribute : Attribute
{
    public Image ProviderIcon { get; protected set; }

    public ProviderIconAttribute(Type resourceType, string resourceName)
    {
        var value = ResourceHelper.GetResourceLookup<Image>(resourceType, resourceName);

        this.ProviderIcon = value;
    }
}

    //From https://dev59.com/6nM_5IYBdhLWcg3w9oSG
    //Only thing I changed was adding NonPublic to binding flags since our images come from other dll's
    // and making it generic, as the original only supports strings
    public class ResourceHelper
    {
        public static T GetResourceLookup<T>(Type resourceType, string resourceName)
        {
            if ((resourceType != null) && (resourceName != null))
            {
                PropertyInfo property = resourceType.GetProperty(resourceName, BindingFlags.Public | BindingFlags.Static | BindingFlags.NonPublic);
                if (property == null)
                {
                    return default(T);
                }

                return (T)property.GetValue(null, null);
            }
            return default(T);
        }
    }

3
我遇到了与属性显示名称有关的问题,我进行了以下更改:
对于我们的资源文件,我将自定义工具属性更改为PublicResXFileCodeGenerator 然后将此添加到属性中:
[Display(Name = "MyResourceName", ResourceType = typeof(Resources.MyResources))]

3
如果您正在使用.NET 3.5或更新版本,您可以使用ErrorMessageResourceNameErrorMessageResourceType参数。
例如:[Required(ErrorMessageResourceName ="attribute_name" , ErrorMessageResourceType = typeof(resource_file_type))]

1

我有一个类似的情况,需要将资源字符串放入属性中。在C# 6中,我们有nameof()功能,似乎可以解决这个问题。

在我的情况下,我可以使用[SomeAttribute(nameof(Resources.SomeResourceKey))],它可以编译通过。然后,在另一端只需要做一些工作,使用该值从资源文件中获取正确的字符串。

在你的情况下,你可以尝试:

[IntegerValidation(1, 70, ErrorMessageResourceKey = nameof(Data.Messages.Speed))]
private int i_Speed;

然后你可以按照以下方式进行操作(伪代码):
Properties.Resources.ResourceManager.GetString(attribute.ErrorMessageResourceKey);

1
使用字符串作为资源的名称。.NET 通过一些内部属性实现这一点。

1
[RegularExpression(".+@.+\..+", ErrorMessageResourceName = "InvalidEmail", ErrorMessageResourceType=typeof(Resources.Registration))] 正则表达式:".+@.+\..+",错误消息资源名称为"InvalidEmail",错误消息资源类型为"Resources.Registration"。 - fireydude

1
属性的特性是,你放入属性属性中的数据必须是常量。这些值将存储在程序集中,但永远不会导致执行的编译代码。因此,你不能有依赖于执行以计算结果的属性值。

0

这是我写的一些东西,因为我找不到其他能做到这一点的东西。

输入

在项目A中编写一个常量字符串类。

[GenerateResource]
public static class ResourceFileName
{
    public static class ThisSupports
    {
        public static class NestedClasses
        {
            [Comment("Comment value")]
            public const string ResourceKey = "Resource Value";
        }
    }
}

输出

然后在项目中将生成一个包含常量类的资源。

enter image description here

你所需要做的就是在某个地方拥有这段代码:

源代码

public class CommentAttribute : Attribute
{
    public CommentAttribute(string comment)
    {
        this.Comment = comment;
    }

    public string Comment { get; set; }
}

public class GenerateResourceAttribute : Attribute
{
    public string FileName { get; set; }
}

public class ResourceGenerator
{
    public ResourceGenerator(IEnumerable<Assembly> assemblies)
    {
        // Loop over the provided assemblies.
        foreach (var assembly in assemblies)
        {
            // Loop over each type in the assembly.
            foreach (var type in assembly.GetTypes())
            {
                // See if the type has the GenerateResource attribute.
                var attribute = type.GetCustomAttribute<GenerateResourceAttribute>(false);
                if (attribute != null)
                {
                    // If so determine the output directory.  First assume it's the current directory.
                    var outputDirectory = Directory.GetCurrentDirectory();

                    // Is this assembly part of the output directory?
                    var index = outputDirectory.LastIndexOf(typeof(ResourceGenerator).Assembly.GetName().Name);
                    if (index >= 0)
                    {
                        // If so remove it and anything after it.
                        outputDirectory = outputDirectory.Substring(0, index);

                        // Is the concatenation of the output directory and the target assembly name not a directory?
                        outputDirectory = Path.Combine(outputDirectory, type.Assembly.GetName().Name);
                        if (!Directory.Exists(outputDirectory))
                        {
                            // If that is the case make it the current directory. 
                            outputDirectory = Directory.GetCurrentDirectory();
                        }
                    }

                    // Use the default file name (Type + "Resources") if one was not provided.
                    var fileName = attribute.FileName;
                    if (fileName == null)
                    {
                        fileName = type.Name + "Resources";
                    }

                    // Add .resx to the end of the file name.
                    fileName = Path.Combine(outputDirectory, fileName);
                    if (!fileName.EndsWith(".resx", StringComparison.InvariantCultureIgnoreCase))
                    {
                        fileName += ".resx";
                    }

                    using (var resx = new ResXResourceWriter(fileName))
                    {
                        var tuples = this.GetTuplesRecursive("", type).OrderBy(t => t.Item1);
                        foreach (var tuple in tuples)
                        {
                            var key = tuple.Item1 + tuple.Item2.Name;

                            var value = tuple.Item2.GetValue(null);

                            string comment = null;
                            var commentAttribute = tuple.Item2.GetCustomAttribute<CommentAttribute>();
                            if (commentAttribute != null)
                            {
                                comment = commentAttribute.Comment;
                            }

                            resx.AddResource(new ResXDataNode(key, value) { Comment = comment });
                        }
                    }
                }
            }
        }
    }

    private IEnumerable<Tuple<string, FieldInfo>> GetTuplesRecursive(string prefix, Type type)
    {
        // Get the properties for the current type.
        foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Static))
        {
            yield return new Tuple<string, FieldInfo>(prefix, field);
        }

        // Get the properties for each child type.
        foreach (var nestedType in type.GetNestedTypes())
        {
            foreach (var tuple in this.GetTuplesRecursive(prefix + nestedType.Name, nestedType))
            {
                yield return tuple;
            }
        }
    }
}

然后创建一个小项目,其中包含对所有程序集的引用,并使用[GenerateResource]进行资源生成。

public class Program
{
    static void Main(string[] args)
    {
        var assemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();
        string path = Directory.GetCurrentDirectory();
        foreach (string dll in Directory.GetFiles(path, "*.dll"))
        {
            assemblies.Add(Assembly.LoadFile(dll));
        }
        assemblies = assemblies.Distinct().ToList();

        new ResourceGenerator(assemblies);
    }
}

然后你的属性可以使用静态类 ResourceFileName.ThisSupports.NestedClasses.ResourceKey,而其他代码可以使用资源文件。
你可能需要根据自己的需要进行调整。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接