如果它可以帮助任何人,我改进了 @AVH 的 answer 以使用递归获取更多信息。
我的目标是创建一个自动生成的 API 帮助页面:
Assembly.GetAssembly(typeof(MyBaseApiController)).GetTypes()
.Where(type => type.IsSubclassOf(typeof(MyBaseApiController)))
.SelectMany(type => type.GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public))
.Where(m => !m.GetCustomAttributes(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), true).Any())
.Select(x => new ApiHelpEndpointViewModel
{
Endpoint = x.DeclaringType.Name.Replace("Controller", String.Empty),
Controller = x.DeclaringType.Name,
Action = x.Name,
DisplayableName = x.GetCustomAttributes<DisplayAttribute>().FirstOrDefault()?.Name ?? x.Name,
Description = x.GetCustomAttributes<DescriptionAttribute>().FirstOrDefault()?.Description ?? String.Empty,
Properties = x.ReturnType.GenericTypeArguments.FirstOrDefault()?.GetProperties(),
PropertyDescription = x.ReturnType.GenericTypeArguments.FirstOrDefault()?.GetProperties()
.Select(q => q.CustomAttributes.SingleOrDefault(a => a.AttributeType.Name == "DescriptionAttribute")?.ConstructorArguments ?? new List<CustomAttributeTypedArgument>() )
.ToList()
})
.OrderBy(x => x.Controller)
.ThenBy(x => x.Action)
.ToList()
.ForEach(x => apiHelpViewModel.Endpoints.Add(x)); //See comment below
(只需更改最后一个 ForEach() 子句,因为我的模型封装在另一个模型中)。
对应的ApiHelpViewModel是:
public class ApiHelpEndpointViewModel
{
public string Endpoint { get; set; }
public string Controller { get; set; }
public string Action { get; set; }
public string DisplayableName { get; set; }
public string Description { get; set; }
public string EndpointRoute => $"/api/{Endpoint}";
public PropertyInfo[] Properties { get; set; }
public List<IList<CustomAttributeTypedArgument>> PropertyDescription { get; set; }
}
当我的端点返回 IQueryable<CustomType> 时,最后一个属性 (PropertyDescription) 包含许多与 CustomType 的属性相关的元数据。因此,您可以获得每个CustomType's 属性的名称、类型、描述(添加[Description] 注释)等。
它比原来的问题更进一步,但如果它可以帮助某人......
更新
更进一步,如果您想在无法修改的字段上添加一些 [DataAnnotation](因为它们是由例如模板生成的),您可以创建一个 MetadataAttributes 类:
[MetadataType(typeof(MetadataAttributesMyClass))]
public partial class MyClass
{
}
public class MetadataAttributesMyClass
{
[Description("My custom description")]
public int Id {get; set;}
//all your generated fields with [Description] or other data annotation
}
小心:MyClass 必须:
- 部分类,
- 在与生成的
MyClass相同的命名空间中
然后,更新检索元数据的代码:
Assembly.GetAssembly(typeof(MyBaseController)).GetTypes()
.Where(type => type.IsSubclassOf(typeof(MyBaseController)))
.SelectMany(type => type.GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public))
.Where(m => !m.GetCustomAttributes(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), true).Any())
.Select(x =>
{
var type = x.ReturnType.GenericTypeArguments.FirstOrDefault();
var metadataType = type.GetCustomAttributes(typeof(MetadataTypeAttribute), true)
.OfType<MetadataTypeAttribute>().FirstOrDefault();
var metaData = (metadataType != null)
? ModelMetadataProviders.Current.GetMetadataForType(null, metadataType.MetadataClassType)
: ModelMetadataProviders.Current.GetMetadataForType(null, type);
return new ApiHelpEndpoint
{
Endpoint = x.DeclaringType.Name.Replace("Controller", String.Empty),
Controller = x.DeclaringType.Name,
Action = x.Name,
DisplayableName = x.GetCustomAttributes<DisplayAttribute>().FirstOrDefault()?.Name ?? x.Name,
Description = x.GetCustomAttributes<DescriptionAttribute>().FirstOrDefault()?.Description ?? String.Empty,
Properties = x.ReturnType.GenericTypeArguments.FirstOrDefault()?.GetProperties(),
PropertyDescription = metaData.Properties.Select(e =>
{
var m = metaData.ModelType.GetProperty(e.PropertyName)
.GetCustomAttributes(typeof(DescriptionAttribute), true)
.FirstOrDefault();
return m != null ? ((DescriptionAttribute)m).Description : string.Empty;
}).ToList()
};
})
.OrderBy(x => x.Controller)
.ThenBy(x => x.Action)
.ToList()
.ForEach(x => api2HelpViewModel.Endpoints.Add(x));
(归功于this answer)
并将PropertyDescription 更新为public List<string> PropertyDescription { get; set; }