PropertyGrid에 표시된 속성 이름을 지역화하는 방법을 찾고 있습니다. 속성 이름은 DisplayNameAttribute 특성을 사용하여 “무시”될 수 있습니다. 불행히도 속성은 상수가 아닌 표현식을 가질 수 없습니다. 따라서 다음과 같은 강력한 형식의 리소스를 사용할 수 없습니다.
class Foo
{
[DisplayAttribute(Resources.MyPropertyNameLocalized)] // do not compile
string MyProperty {get; set;}
}
주위를 둘러 보았고 리소스를 사용할 수 있도록 DisplayNameAttribute에서 상속하라는 제안을 찾았습니다. 나는 다음과 같은 코드로 끝날 것입니다.
class Foo
{
[MyLocalizedDisplayAttribute("MyPropertyNameLocalized")] // not strongly typed
string MyProperty {get; set;}
}
그러나 나는 확실히 좋은 것이 아닌 강력한 유형의 자원 이점을 잃습니다. 그런 다음 내가 찾고있는 DisplayNameResourceAttribute 를 발견했습니다. 하지만 Microsoft.VisualStudio.Modeling.Design 네임 스페이스에 있어야하며이 네임 스페이스에 대해 추가해야하는 참조를 찾을 수 없습니다.
좋은 방법으로 DisplayName 지역화를 달성하는 더 쉬운 방법이 있는지 아는 사람이 있습니까? 또는 Microsoft가 Visual Studio에 사용하는 것처럼 보이는 것을 사용하는 방법이 있습니까?
답변
.NET 4에는 System.ComponentModel.DataAnnotations 의 Display 속성이 있습니다 PropertyGrid
. MVC 3에서 작동합니다 .
[Display(ResourceType = typeof(MyResources), Name = "UserName")]
public string UserName { get; set; }
라는 이름의 자원까지이 모양 UserName
당신에 MyResources
된 .resx 파일.
답변
여러 언어를 지원하기 위해 여러 속성에 대해이 작업을 수행하고 있습니다. Microsoft와 유사한 접근 방식을 사용하여 기본 속성을 재정의하고 실제 문자열이 아닌 리소스 이름을 전달합니다. 그런 다음 리소스 이름을 사용하여 반환 할 실제 문자열을 DLL 리소스에서 조회합니다.
예를 들면 :
class LocalizedDisplayNameAttribute : DisplayNameAttribute
{
private readonly string resourceName;
public LocalizedDisplayNameAttribute(string resourceName)
: base()
{
this.resourceName = resourceName;
}
public override string DisplayName
{
get
{
return Resources.ResourceManager.GetString(this.resourceName);
}
}
}
실제로 속성을 사용할 때 한 단계 더 나아가 리소스 이름을 정적 클래스의 상수로 지정할 수 있습니다. 그렇게하면 다음과 같은 선언을 얻을 수 있습니다.
[LocalizedDisplayName(ResourceStrings.MyPropertyName)]
public string MyProperty
{
get
{
...
}
}
업데이트
ResourceStrings
는 다음과 같습니다 (각 문자열은 실제 문자열을 지정하는 리소스의 이름을 나타냄).
public static class ResourceStrings
{
public const string ForegroundColorDisplayName="ForegroundColorDisplayName";
public const string FontSizeDisplayName="FontSizeDisplayName";
}
답변
다음은 별도의 어셈블리 (제 경우에는 “공통”이라고 함)에서 사용한 솔루션입니다.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Event)]
public class DisplayNameLocalizedAttribute : DisplayNameAttribute
{
public DisplayNameLocalizedAttribute(Type resourceManagerProvider, string resourceKey)
: base(Utils.LookupResource(resourceManagerProvider, resourceKey))
{
}
}
리소스를 조회하는 코드 :
internal static string LookupResource(Type resourceManagerProvider, string resourceKey)
{
foreach (PropertyInfo staticProperty in resourceManagerProvider.GetProperties(BindingFlags.Static | BindingFlags.NonPublic))
{
if (staticProperty.PropertyType == typeof(System.Resources.ResourceManager))
{
System.Resources.ResourceManager resourceManager = (System.Resources.ResourceManager)staticProperty.GetValue(null, null);
return resourceManager.GetString(resourceKey);
}
}
return resourceKey; // Fallback with the key name
}
일반적인 사용법은 다음과 같습니다.
class Foo
{
[Common.DisplayNameLocalized(typeof(Resources.Resource), "CreationDateDisplayName"),
Common.DescriptionLocalized(typeof(Resources.Resource), "CreationDateDescription")]
public DateTime CreationDate
{
get;
set;
}
}
리소스 키에 리터럴 문자열을 사용하므로 매우 추한 것은 무엇입니까? 상수를 사용하는 것은 아마도 좋은 생각이 아닌 Resources.Designer.cs를 수정하는 것을 의미합니다.
결론 : 나는 그것에 만족하지 않지만 그러한 일반적인 작업에 유용한 것을 제공 할 수없는 Microsoft에 대해서는 훨씬 덜 만족합니다.
답변
은 Using 디스플레이 (System.ComponentModel.DataAnnotations에서) 속성과 nameof () C # 6의 발현을, 당신은 지역화 및 강력한 형식의 솔루션을 얻을 수 있습니다.
[Display(ResourceType = typeof(MyResources), Name = nameof(MyResources.UserName))]
public string UserName { get; set; }
답변
T4를 사용하여 상수를 생성 할 수 있습니다. 나는 하나를 썼다.
<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Xml.dll" #>
<#@ import namespace="System.Xml" #>
<#@ import namespace="System.Xml.XPath" #>
using System;
using System.ComponentModel;
namespace Bear.Client
{
/// <summary>
/// Localized display name attribute
/// </summary>
public class LocalizedDisplayNameAttribute : DisplayNameAttribute
{
readonly string _resourceName;
/// <summary>
/// Initializes a new instance of the <see cref="LocalizedDisplayNameAttribute"/> class.
/// </summary>
/// <param name="resourceName">Name of the resource.</param>
public LocalizedDisplayNameAttribute(string resourceName)
: base()
{
_resourceName = resourceName;
}
/// <summary>
/// Gets the display name for a property, event, or public void method that takes no arguments stored in this attribute.
/// </summary>
/// <value></value>
/// <returns>
/// The display name.
/// </returns>
public override String DisplayName
{
get
{
return Resources.ResourceManager.GetString(this._resourceName);
}
}
}
partial class Constants
{
public partial class Resources
{
<#
var reader = XmlReader.Create(Host.ResolvePath("resources.resx"));
var document = new XPathDocument(reader);
var navigator = document.CreateNavigator();
var dataNav = navigator.Select("/root/data");
foreach (XPathNavigator item in dataNav)
{
var name = item.GetAttribute("name", String.Empty);
#>
public const String <#= name#> = "<#= name#>";
<# } #>
}
}
}
답변
이것은 오래된 질문이지만 이것이 매우 일반적인 문제라고 생각하며 여기에 MVC 3의 솔루션이 있습니다.
첫째, 불쾌한 문자열을 피하기 위해 상수를 생성하려면 T4 템플릿이 필요합니다. 모든 레이블 문자열을 포함하는 리소스 파일 ‘Labels.resx’가 있습니다. 따라서 T4 템플릿은 리소스 파일을 직접 사용합니다.
<#@ template debug="True" hostspecific="True" language="C#" #>
<#@ output extension=".cs" #>
<#@ Assembly Name="C:\Project\trunk\Resources\bin\Development\Resources.dll" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.Globalization" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Resources" #>
<#
var resourceStrings = new List<string>();
var manager = Resources.Labels.ResourceManager;
IDictionaryEnumerator enumerator = manager.GetResourceSet(CultureInfo.CurrentCulture, true, true)
.GetEnumerator();
while (enumerator.MoveNext())
{
resourceStrings.Add(enumerator.Key.ToString());
}
#>
// This file is generated automatically. Do NOT modify any content inside.
namespace Lib.Const{
public static class LabelNames{
<#
foreach (String label in resourceStrings){
#>
public const string <#=label#> = "<#=label#>";
<#
}
#>
}
}
그런 다음 ‘DisplayName’을 지역화하는 확장 메서드가 생성됩니다.
using System.ComponentModel.DataAnnotations;
using Resources;
namespace Web.Extensions.ValidationAttributes
{
public static class ValidationAttributeHelper
{
public static ValidationContext LocalizeDisplayName(this ValidationContext context)
{
context.DisplayName = Labels.ResourceManager.GetString(context.DisplayName) ?? context.DisplayName;
return context;
}
}
}
‘DisplayName’속성은 ‘Labels.resx’에서 자동으로 읽기 위해 ‘DisplayLabel’속성으로 대체됩니다.
namespace Web.Extensions.ValidationAttributes
{
public class DisplayLabelAttribute :System.ComponentModel.DisplayNameAttribute
{
private readonly string _propertyLabel;
public DisplayLabelAttribute(string propertyLabel)
{
_propertyLabel = propertyLabel;
}
public override string DisplayName
{
get
{
return _propertyLabel;
}
}
}
}
모든 준비 작업이 끝나면 기본 유효성 검사 속성을 살펴볼 시간입니다. ‘필수’속성을 예로 사용하고 있습니다.
using System.ComponentModel.DataAnnotations;
using Resources;
namespace Web.Extensions.ValidationAttributes
{
public class RequiredAttribute : System.ComponentModel.DataAnnotations.RequiredAttribute
{
public RequiredAttribute()
{
ErrorMessageResourceType = typeof (Errors);
ErrorMessageResourceName = "Required";
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
return base.IsValid(value, validationContext.LocalizeDisplayName());
}
}
}
이제 모델에 이러한 속성을 적용 할 수 있습니다.
using Web.Extensions.ValidationAttributes;
namespace Web.Areas.Foo.Models
{
public class Person
{
[DisplayLabel(Lib.Const.LabelNames.HowOldAreYou)]
public int Age { get; set; }
[Required]
public string Name { get; set; }
}
}
기본적으로 속성 이름은 ‘Label.resx’를 조회하는 키로 사용되지만 ‘DisplayLabel’을 통해 설정하면 대신 사용됩니다.
답변
메서드 중 하나를 재정 의하여 DisplayNameAttribute를 하위 클래스로 만들어 i18n을 제공 할 수 있습니다. 그렇게. 편집 : 키에 상수를 사용하는 데 만족해야 할 수도 있습니다.
using System;
using System.ComponentModel;
using System.Windows.Forms;
class Foo {
[MyDisplayName("bar")] // perhaps use a constant: SomeType.SomeResName
public string Bar {get; set; }
}
public class MyDisplayNameAttribute : DisplayNameAttribute {
public MyDisplayNameAttribute(string key) : base(Lookup(key)) {}
static string Lookup(string key) {
try {
// get from your resx or whatever
return "le bar";
} catch {
return key; // fallback
}
}
}
class Program {
[STAThread]
static void Main() {
Application.Run(new Form { Controls = {
new PropertyGrid { SelectedObject =
new Foo { Bar = "abc" } } } });
}
}