属性があり、リソースファイルから属性にテキストをロードしたい。
[IntegerValidation(1, 70, ErrorMessage = Data.Messages.Speed)]
private int i_Speed;
しかし、「属性引数は、定数式、typeof式、または属性パラメーターtypeの配列作成式でなければなりません」というメッセージが表示され続けます
次のように、Data.Messages.Textの代わりに文字列を追加すると完全に機能します。
[IntegerValidation(1, 70, ErrorMessage = "Invalid max speed")]
何か案は?
属性値は、コンパイル時にアセンブリにハードコードされます。実行時に何かを実行したい場合は、定数をkeyとして使用してから、属性クラス自体にコードを挿入してリソースをロードする必要があります。
これが私の解決策です。 MicrosoftがDataAnnotationsで行ったように、resourceNameプロパティとresourceTypeプロパティを属性に追加しました。
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;
}
}
これが私がまとめたものの修正版です:
[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 http://stackoverflow.com/questions/1150874/c-sharp-attribute-text-from-resource-file
//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);
}
}
属性の表示名でこの問題に遭遇し、次の変更を加えました。
リソースファイルでは、カスタムツールのプロパティをPublicResXFileCodeGenerator
に変更しました
次に、これを属性に追加しました。
[Display(Name = "MyResourceName", ResourceType = typeof(Resources.MyResources))]
リソースの名前である文字列を使用します。 .NETは、いくつかの内部属性を使用してこれを実行します。
属性の性質上、属性プロパティに入力するデータは定数でなければなりません。これらの値はアセンブリ内に格納されますが、コンパイルされたコードが実行されることはありません。したがって、結果を計算するために実行に依存する属性値を持つことはできません。
同様のケースがあり、リソース文字列を属性に配置する必要があります。 C#6には、nameof()
機能があり、それでうまくいくようです。
私の場合、[SomeAttribute(nameof(Resources.SomeResourceKey))]
を使用でき、正常にコンパイルされます。次に、その値を使用してResourcesファイルから正しい文字列を取得するために、もう一方の端で少し作業を行う必要があります。
あなたの場合、あなたは試みるかもしれません:
[IntegerValidation(1, 70, ErrorMessageResourceKey = nameof(Data.Messages.Speed))]
private int i_Speed;
次に、(擬似コード)の行に沿って何かを行うことができます:
Properties.Resources.ResourceManager.GetString(attribute.ErrorMessageResourceKey);
これを行うものが他に見つからなかったので、これが私が書いたものです。
入力
プロジェクトAに定数文字列クラスを記述します。
[GenerateResource]
public static class ResourceFileName
{
public static class ThisSupports
{
public static class NestedClasses
{
[Comment("Comment value")]
public const string ResourceKey = "Resource Value";
}
}
}
出力
そして、定数クラスを含むプロジェクトでリソースが生成されます。
あなたがする必要があるのはどこかにこのコードを持っていることです:
ソース
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
を使用でき、他のコードはリソースファイルを使用できます。
特定のニーズに合わせて調整する必要があるかもしれません。
.NET 3.5以降を使用している場合は、ErrorMessageResourceName
およびErrorMessageResourceType
パラメーターを使用できます。
例:[Required(ErrorMessageResourceName ="attribute_name" , ErrorMessageResourceType = typeof(resource_file_type))]