diff --git a/src/BootstrapBlazor.Server/Components/Components/AttributeTable.razor b/src/BootstrapBlazor.Server/Components/Components/AttributeTable.razor
index 48f3aabc969..adeda25536b 100644
--- a/src/BootstrapBlazor.Server/Components/Components/AttributeTable.razor
+++ b/src/BootstrapBlazor.Server/Components/Components/AttributeTable.razor
@@ -1,13 +1,16 @@
-
+
@Title
-
+
diff --git a/src/BootstrapBlazor.Server/Components/Components/AttributeTable.razor.cs b/src/BootstrapBlazor.Server/Components/Components/AttributeTable.razor.cs
index 9d5f1ab4b8c..3f4b848654e 100644
--- a/src/BootstrapBlazor.Server/Components/Components/AttributeTable.razor.cs
+++ b/src/BootstrapBlazor.Server/Components/Components/AttributeTable.razor.cs
@@ -1,4 +1,4 @@
-// Licensed to the .NET Foundation under one or more agreements.
+// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
@@ -6,7 +6,7 @@
namespace BootstrapBlazor.Server.Components.Components;
///
-///
+/// 组件参数表格组件
///
public sealed partial class AttributeTable
{
@@ -15,24 +15,42 @@ public sealed partial class AttributeTable
private IStringLocalizer? Localizer { get; set; }
///
- ///
+ /// 获得/设置 表格标题
///
[Parameter]
[NotNull]
public string? Title { get; set; }
///
- ///
+ /// 获得/设置 表格数据
///
- [Parameter] public IEnumerable? Items { get; set; }
+ [Parameter]
+ public IEnumerable Items { get; set; } = [];
+
+ ///
+ /// 获得/设置 表格关联组件类型
+ ///
+ [Parameter]
+ public Type? Type { get; set; }
///
- /// OnInitialized 方法
+ /// 是否显示合计信息 默认 false
+ ///
+ [Parameter]
+ public bool ShowFooter { get; set; }
+
+ ///
+ ///
///
protected override void OnInitialized()
{
base.OnInitialized();
Title ??= Localizer[nameof(Title)];
+
+ if (Type != null)
+ {
+ Items = ComponentAttributeCacheService.GetAttributes(Type);
+ }
}
}
diff --git a/src/BootstrapBlazor.Server/Components/Samples/Circles.razor b/src/BootstrapBlazor.Server/Components/Samples/Circles.razor
index cf5740f3201..89f2fa54a1a 100644
--- a/src/BootstrapBlazor.Server/Components/Samples/Circles.razor
+++ b/src/BootstrapBlazor.Server/Components/Samples/Circles.razor
@@ -12,15 +12,17 @@
-
-
-
+
+
+
+
+
@@ -53,4 +55,4 @@
-
+
diff --git a/src/BootstrapBlazor.Server/Components/Samples/Circles.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/Circles.razor.cs
index 897ed8eff9e..c0c58581cd3 100644
--- a/src/BootstrapBlazor.Server/Components/Samples/Circles.razor.cs
+++ b/src/BootstrapBlazor.Server/Components/Samples/Circles.razor.cs
@@ -1,4 +1,4 @@
-// Licensed to the .NET Foundation under one or more agreements.
+// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
@@ -17,61 +17,5 @@ private void Add(int interval)
_circleValue += interval;
_circleValue = Math.Min(100, Math.Max(0, _circleValue));
}
-
- ///
- /// GetAttributes
- ///
- ///
- private AttributeItem[] GetAttributes() =>
- [
- new()
- {
- Name = "Width",
- Description = Localizer["Width"],
- Type = "int",
- ValueList = "",
- DefaultValue = "120"
- },
- new()
- {
- Name = "StrokeWidth",
- Description = Localizer["StrokeWidth"],
- Type = "int",
- ValueList = "",
- DefaultValue = "2"
- },
- new()
- {
- Name = "Value",
- Description = Localizer["Value"],
- Type = "int",
- ValueList = "0-100",
- DefaultValue = "0"
- },
- new()
- {
- Name = "Color",
- Description = Localizer["Color"],
- Type = "Color",
- ValueList = "Primary / Secondary / Success / Danger / Warning / Info / Dark",
- DefaultValue = "Primary"
- },
- new()
- {
- Name = "ShowProgress",
- Description = Localizer["ShowProgress"],
- Type = "bool",
- ValueList = "true / false",
- DefaultValue = "true"
- },
- new()
- {
- Name = "ChildContent",
- Description = Localizer["ChildContent"],
- Type = "RenderFragment",
- ValueList = "",
- DefaultValue = ""
- }
- ];
}
diff --git a/src/BootstrapBlazor.Server/Data/AttributeItem.cs b/src/BootstrapBlazor.Server/Data/AttributeItem.cs
index fe24314716a..e761968fd09 100644
--- a/src/BootstrapBlazor.Server/Data/AttributeItem.cs
+++ b/src/BootstrapBlazor.Server/Data/AttributeItem.cs
@@ -1,10 +1,8 @@
-// Licensed to the .NET Foundation under one or more agreements.
+// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
-using System.ComponentModel;
-
namespace BootstrapBlazor.Server.Data;
///
@@ -15,30 +13,30 @@ public class AttributeItem
///
/// 获得/设置 参数
///
- [DisplayName("参数")]
public string Name { get; set; } = "";
///
/// 获得/设置 说明
///
- [DisplayName("说明")]
public string Description { get; set; } = "";
///
/// 获得/设置 类型
///
- [DisplayName("类型")]
public string Type { get; set; } = "";
///
/// 获得/设置 可选值
///
- [DisplayName("可选值")]
public string ValueList { get; set; } = "";
///
- /// 获得/设置 默认值
+ /// 获得/设置 版本
+ ///
+ public string Version { get; set; } = "";
+
+ ///
+ ///
///
- [DisplayName("默认值")]
public string DefaultValue { get; set; } = "";
}
diff --git a/src/BootstrapBlazor.Server/Directory.Build.targets b/src/BootstrapBlazor.Server/Directory.Build.targets
index 7e382f03fe4..4beb8250df6 100644
--- a/src/BootstrapBlazor.Server/Directory.Build.targets
+++ b/src/BootstrapBlazor.Server/Directory.Build.targets
@@ -1,9 +1,17 @@
-
+
+
+
+
+
+
+
+
+
diff --git a/src/BootstrapBlazor.Server/Services/ComponentAttributeCacheService.cs b/src/BootstrapBlazor.Server/Services/ComponentAttributeCacheService.cs
new file mode 100644
index 00000000000..b7cb7406b85
--- /dev/null
+++ b/src/BootstrapBlazor.Server/Services/ComponentAttributeCacheService.cs
@@ -0,0 +1,207 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the Apache 2.0 License
+// See the LICENSE file in the project root for more information.
+// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
+
+using System.Collections.Concurrent;
+using System.Globalization;
+using System.Reflection;
+using System.Xml;
+using System.Xml.Linq;
+
+namespace BootstrapBlazor.Server.Services;
+
+///
+/// 组件属性缓存服务
+///
+public static class ComponentAttributeCacheService
+{
+ private static readonly ConcurrentDictionary> _cache = new();
+
+ ///
+ /// 通过组件类型获取组件的 AttributeItem 列表
+ ///
+ public static List GetAttributes(Type componentType)
+ {
+#if DEBUG
+ return GetAttributeCore(componentType);
+#else
+ var currentLanguage = CultureInfo.CurrentUICulture.TwoLetterISOLanguageName;
+ var key = $"{componentType.FullName}_{currentLanguage}";
+ return _cache.GetOrAdd(key, _ => GetAttributeCore(componentType));
+#endif
+ }
+
+ private static List GetAttributeCore(Type type)
+ {
+ var attributes = new List();
+ var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
+ .Where(p => p.GetCustomAttribute() != null);
+
+ var xmlDoc = GetXmlDocumentation(type.Assembly);
+ foreach (var property in properties)
+ {
+ var item = new AttributeItem
+ {
+ Name = property.Name,
+ Type = GetFriendlyTypeName(property.PropertyType),
+ Description = GetSummary(xmlDoc, property) ?? "",
+ Version = GetVersion(xmlDoc, property) ?? "10.0.0"
+ };
+ attributes.Add(item);
+ }
+ return attributes.OrderBy(i => i.Name).ToList();
+ }
+
+ ///
+ /// 从 XML 注释获取 summary(支持多语言)
+ ///
+ private static string? GetSummary(XDocument? xmlDoc, PropertyInfo property)
+ {
+ if (xmlDoc == null) return null;
+
+ var memberName = $"P:{property.DeclaringType?.FullName}.{property.Name}";
+ var memberElement = xmlDoc.Descendants("member")
+ .FirstOrDefault(x => x.Attribute("name")?.Value == memberName);
+
+ if (memberElement == null) return null;
+
+ var summaryElement = memberElement.Element("summary");
+ if (summaryElement == null) return null;
+
+ // 获取当前语言(zh-CN -> zh, en-US -> en)
+ var currentLanguage = CultureInfo.CurrentUICulture.TwoLetterISOLanguageName;
+
+ // 查找匹配当前语言的 para 元素
+ var langPara = summaryElement.Elements("para")
+ .FirstOrDefault(p => p.Attribute("lang")?.Value == currentLanguage);
+
+ if (langPara != null)
+ {
+ return langPara.Value.Trim();
+ }
+
+ // 如果找不到当前语言,回退到第一个有 lang 属性的 para(通常是 zh)
+ var firstLangPara = summaryElement.Elements("para")
+ .FirstOrDefault(p => p.Attribute("lang") != null);
+
+ if (firstLangPara != null)
+ {
+ return firstLangPara.Value.Trim();
+ }
+
+ // 如果都没有,返回整个 summary 的文本(向后兼容旧格式)
+ return summaryElement.Value.Trim();
+ }
+
+ ///
+ /// 从 XML 注释的 para version 节点获取版本信息
+ ///
+ private static string? GetVersion(XDocument? xmlDoc, PropertyInfo property)
+ {
+ if (xmlDoc == null) return null;
+
+ var memberName = $"P:{property.DeclaringType?.FullName}.{property.Name}";
+ var memberElement = xmlDoc.Descendants("member")
+ .FirstOrDefault(x => x.Attribute("name")?.Value == memberName);
+
+ if (memberElement == null) return null;
+
+ // 在 summary 节点下查找包含 version 的 para 节点
+ // XML 格式: 10.2.2
+ var summaryElement = memberElement.Element("summary");
+ if (summaryElement == null) return null;
+
+ // 查找第一个包含 version 元素的 para
+ // 直接在循环中返回,避免创建中间变量
+ return summaryElement.Elements("para")
+ .Select(p => p.Element("version"))
+ .FirstOrDefault(v => v != null)
+ ?.Value.Trim();
+ }
+
+ ///
+ /// 获取友好的类型名称
+ ///
+ private static string GetFriendlyTypeName(Type type)
+ {
+ if (type.IsGenericType)
+ {
+ var genericTypeName = type.GetGenericTypeDefinition().Name;
+ var backtickIndex = genericTypeName.IndexOf('`');
+ if (backtickIndex > 0)
+ {
+ genericTypeName = genericTypeName.Substring(0, backtickIndex);
+ }
+ var genericArgs = string.Join(", ", type.GetGenericArguments().Select(GetFriendlyTypeName));
+ return $"{genericTypeName}<{genericArgs}>";
+ }
+
+ return type.Name switch
+ {
+ "Int32" => "int",
+ "String" => "string",
+ "Boolean" => "bool",
+ "Double" => "double",
+ "Decimal" => "decimal",
+ _ => type.Name
+ };
+ }
+
+ ///
+ /// 获取 XML 文档
+ ///
+ private static XDocument? GetXmlDocumentation(Assembly? assembly)
+ {
+ if (assembly == null) return null;
+
+ try
+ {
+ var assemblyLocation = assembly.Location;
+ if (string.IsNullOrEmpty(assemblyLocation))
+ {
+ return null;
+ }
+
+ var xmlPath = Path.Combine(
+ Path.GetDirectoryName(assemblyLocation) ?? "",
+ Path.GetFileNameWithoutExtension(assemblyLocation) + ".xml"
+ );
+
+ if (File.Exists(xmlPath))
+ {
+ // 使用安全的 XML 读取设置防止 XXE 攻击
+ var settings = new XmlReaderSettings
+ {
+ DtdProcessing = DtdProcessing.Prohibit,
+ XmlResolver = null
+ };
+
+ using var reader = XmlReader.Create(xmlPath, settings);
+ return XDocument.Load(reader);
+ }
+ }
+ catch (FileNotFoundException)
+ {
+ // XML 文档文件不存在,忽略
+ }
+ catch (XmlException)
+ {
+ // XML 文档格式错误,忽略
+ }
+ catch (UnauthorizedAccessException)
+ {
+ // 没有访问权限,忽略
+ }
+
+ return null;
+ }
+
+ ///
+ /// 清除缓存
+ ///
+ public static void ClearCache()
+ {
+ _cache.Clear();
+ }
+}