【问题标题】:C# data annotation, can't extend a specific attribute - what are the possibilities?C# 数据注释,不能扩展特定属性 - 有哪些可能性?
【发布时间】:2020-08-12 04:47:55
【问题描述】:

我正在使用 ASP.NET 动态数据构建旧版应用程序。与往常一样,模型都是只读的,可以通过属性设置显示名称或描述。

这很好,但是,现在我需要查询两个不同的源(资源文件和其他一些源)以获取显示名称。

之前的代码是干净的,因为我们只查询了资源:

[Display(ResourceType = typeof(Resources.m_Res), Name = "M_RES_MIMETYPE_ID", Description = "M_RES_MIMETYPE_ID_DESCR")]

这完全没问题,并且按预期工作。但是,现在我必须首先从其他文件中获取显示名称和描述,如果其他一切都失败了,我必须回退到资源。

我必须以这种方式创建两个不同的自定义属性:

    public class MGADisplayName : DisplayNameAttribute
    {
          private readonly string propertyName;
          public string Name { get; set; }
          public Type TableName { get; set; }
          public Type ResourceType { get; set; }

          public MGADisplayName([CallerMemberName] string PropertyName = null)
          {
              propertyName = PropertyName;
          }

          public override string DisplayName
          {
              get
              {
                  var key = (TableName.Name + ":" + (propertyName ?? Name)).ToLower();
                  if (/* SOME QUERYING */)
                  {
                      return QUERY[key];
                  }
                  else
                  {
                      string property = Resources.m_Res.ResourceManager.GetString(Name);
                      if (property != null)
                      {
                          return property;
                      }
                      else
                      {
                          return Name;
                      }

                  }
              }
          }
    }

这种工作,我想暂时还可以,但下一个问题指日可待:我需要对 Display.GroupName 做同样的事情。

现在,据我所知,没有要扩展的 GroupNameAttribute,所以我在这里有点摸不着头脑。

我希望我可以扩展 DisplayAttribute,这正是我需要的,但是这个类是密封的,所以这是一个死胡同。

我希望我可以即时更改模型并通过 setter 提供 DisplayName 和 Description,但模型只有 getter,所以这是另一个死胡同。

我这里的选项慢慢用完了。这里还能做什么?

【问题讨论】:

  • 我没有意识到你为什么坚持扩展 .net 的预定义属性而不是创建全新的属性??!
  • 因为我使用的是由 .net 创建的模型。这意味着默认情况下,每个 .aspx 模板都使用 GroupName 和 Description。我可以创建一个自定义 MyGroupNameAttribute,但 GroupName 值属性将被忽略。
  • 在运行时添加属性会有帮助吗?
  • 在这一点上,我并没有排除任何东西:)你有什么想法?

标签: c# asp.net custom-attributes asp.net-dynamic-data


【解决方案1】:

虽然DisplayAttribute类是密封的,但可以通过ResourceType属性自定义:

获取或设置包含ShortNameNamePromptDescription 属性的资源的类型。

有两件事要提。首先,它还处理GroupName。第二个在备注部分提到:

如果此值不是null,则假定字符串属性是返回实际字符​​串值的公共静态属性的名称。

简而言之,提供的类型可以是任何 public 类/结构,每个字符串键都具有 public static string 属性。

这完全符合PublicResXFileCodeGenerator 生成的类型化资源类。但重要的是它可以是任意类型,您可以利用这一事实。

例如:

public static class SR
{
    // Assuming you have file SR.resx under same namespace
    static readonly ResourceManager ResourceManager = new ResourceManager(typeof(SR));

    static string GetString([CallerMemberName]string propertyName = null)
    {
        // Custom logic goes here
        return ResourceManager.GetString(propertyName) ?? propertyName;
    }

    // Properties
    public static string M_RES_MIMETYPE_ID => GetString();
    public static string M_RES_MIMETYPE_ID_DESCR => GetString();
    public static string M_RES_MIMETYPE_ID_GROUP => GetString();
    // ...
}

用法:

[Display(
    ResourceType = typeof(SR), 
    Name = nameof(SR.M_RES_MIMETYPE_ID),
    Description = nameof(SR.M_RES_MIMETYPE_ID_DESCR),
    GroupName = nameof(SR.M_RES_MIMETYPE_ID_GROUP)
)]

测试:

var displayInfo = new DisplayAttribute
{
    ResourceType = typeof(SR),
    Name = nameof(SR.M_RES_MIMETYPE_ID),
    Description = nameof(SR.M_RES_MIMETYPE_ID_DESCR),
    GroupName = nameof(SR.M_RES_MIMETYPE_ID_GROUP)
};
// These are used by ASP.NET for retrieving actual values
var name = displayInfo.GetName();
var description = displayInfo.GetDescription();
var groupName = displayInfo.GetGroupName();

所以,这就是解决方案。

现在,唯一的问题是需要为每个资源键手动添加string 属性。它可以通过推出您自己的单个文件生成器或 T4 模板生成器来解决,但我认为如何做到这一点超出了本文的范围。

【讨论】:

    【解决方案2】:

    正如您所提到的,DisplayAttribute 已被密封。对于自定义场景,我们需要添加自己的自定义属性。但是为了简化自定义属性的实现,我们可以使用包装器模式来重用原始代码以及添加我们自己的自定义代码。像这样:

    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Method, AllowMultiple = false)]
        public class CustomDisplayAttribute : Attribute
        {
            private DisplayAttribute _innerAttribute;
    
            public CustomDisplayAttribute()
            {
                _innerAttribute = new DisplayAttribute();
            }
    
            public string ShortName
            {
                get
                {
                    return _innerAttribute.ShortName;
                }
                set
                {
                    _innerAttribute.ShortName = value;
                }
            }
    ...
    

    不会选择自定义添加的属性,因为它没有实现DisplayAttribute。要在读取元数据时选择它,我们需要添加一个自定义元数据提供程序。它应该继承自DataAnnotationsModelMetadataProvider。您可以根据需要自定义提供程序代码。

    public class CustomDataAnnotationsModelMetadataProvider : DataAnnotationsModelMetadataProvider
        {
            protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
            {
                var result = base.CreateMetadata(attributes, containerType,
                        modelAccessor, modelType, propertyName);
    
                CustomDisplayAttribute customDisplay = attributes.OfType<CustomDisplayAttribute>().FirstOrDefault();
                if (customDisplay != null)
                {
                    result.Description = customDisplay.GetDescription();
                    result.ShortDisplayName = customDisplay.GetShortName();
                    result.Watermark = customDisplay.GetPrompt();
                    result.Order = customDisplay.GetOrder() ?? ModelMetadata.DefaultOrder;
    
                    result.DisplayName = customDisplay.GetName();
                }
    
                return result;
            }
        }
    

    最后,为了确保使用CustomDataAnnotationsModelMetadataProvider,我们应该将current 元数据提供程序设置为它。我们可以在Global.asax.csStartup.cs 上添加此代码来设置当前值。

    ModelMetadataProviders.Current = new CustomDataAnnotationsModelMetadataProvider();
    

    找到完整的示例代码 here。我在同一目录中的 MVC Web 应用程序上对其进行了测试。随意克隆 repo 并尝试一下。

    顺便说一句,正如另一个答案中提到的,这个解决方案很久以前在here 上讨论过,我只是试图让它更漂亮并添加完整的示例代码

    【讨论】:

    • 这似乎是我需要的解决方案,我愿意重新开放赏金并接受您的回答,但是,我不确定这是否也适用于 Web 表单。执行此操作时出现“当前上下文中不存在名称‘ModelMetadataProviders’”错误:ModelMetadataProviders.Current = new CustomDataAnnotationsModelMetadataProvider();
    • @uglycode 是的,它确实适用于 MVC,我在我的 repo 上共享了示例。对于 WebForms,您如何使用它?能不能分享一下代码,复制一下场景就好了。
    • 我在void Application_Start(object sender, EventArgs e) 中使用它,基本上和你在repo 中使用的方法一样。我不确定ModelMetadataProviders 是否甚至是网络表单的一部分,即我不确定是否可以应用此解决方案?
    • @uglycode 我会尝试做 POC,需要一点时间,因为我不是 .NET forms pro,如果我成功了会回复你。
    • 谢谢,我将不胜感激,因为我担心在网络表单中无法做到这一点:/ 遗憾...
    猜你喜欢
    • 1970-01-01
    • 2011-11-14
    • 2020-02-14
    • 2011-12-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-18
    • 2011-03-05
    相关资源
    最近更新 更多