Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 16, 2026

Summary By Copilot

Replaced manual AttributeItem array maintenance with reflection-based automatic extraction from Circle component parameters. Implemented global static cache for performance.

Changes

AttributeItem class (src/BootstrapBlazor.Server/Data/AttributeItem.cs)

  • Added Version property for XML documentation version tags

ComponentAttributeCacheService (src/BootstrapBlazor.Server/Services/ComponentAttributeCacheService.cs)

  • Global ConcurrentDictionary cache for component attribute metadata
  • Reflection-based extraction of [Parameter] attributes
  • XML documentation parsing: summary, version (<para><version>), default values
  • Automatic enum value list generation
  • Security hardening: XXE prevention via XmlReaderSettings, assembly location validation, specific exception handling

Circles.razor.cs (src/BootstrapBlazor.Server/Components/Samples/Circles.razor.cs)

  • Replaced 52-line manual array with single reflection call:
private AttributeItem[] GetAttributes()
{
    return ComponentAttributeCacheService.GetAttributes(typeof(BootstrapBlazor.Components.Circle));
}

BootstrapBlazor.csproj (src/BootstrapBlazor/BootstrapBlazor.csproj)

  • Enabled GenerateDocumentationFile for XML comments

Screenshot

Circle Attributes Table

Attributes table correctly displays all Circle component parameters via reflection: Value, Width, StrokeWidth, Color (with enum values), ShowProgress, ChildContent, Id, AdditionalAttributes.

Regression?

  • Yes
  • No

Risk

  • High
  • Medium
  • Low

Isolated change to demo component attribute display. No API changes, backward compatible. Cache invalidation available via ClearCache().

Verification

  • Manual (required)
  • Automated

Packaging changes reviewed?

  • Yes
  • No
  • N/A

☑️ Self Check before Merge

⚠️ Please check all items below before review. ⚠️

  • Doc is updated/provided or not needed
  • Demo is updated/provided or not needed
  • Merge the latest code from the main branch
Original prompt

目标

重构 Circles 示例组件,从手动维护 AttributeItem 列表改为使用反射自动获取 Circle 组件的参数信息,并通过全局静态字典缓存提升性能。

需要实现的功能

1. 扩展 AttributeItem 类添加 Version 属性

文件:src/BootstrapBlazor.Server/Data/AttributeItem.cs

在 AttributeItem 类中添加一个新的 Version 属性:

/// <summary>
/// 获得/设置 版本
/// </summary>
[DisplayName("版本")]
public string Version { get; set; } = "";

2. 创建全局静态缓存服务

创建新文件:src/BootstrapBlazor.Server/Services/ComponentAttributeCacheService.cs

实现一个全局静态字典缓存服务,用于缓存组件的 AttributeItem 列表:

using System.Collections.Concurrent;
using System.ComponentModel;
using System.Reflection;
using System.Xml.Linq;
using BootstrapBlazor.Server.Data;
using Microsoft.AspNetCore.Components;

namespace BootstrapBlazor.Server.Services;

/// <summary>
/// 组件属性缓存服务
/// </summary>
public static class ComponentAttributeCacheService
{
    private static readonly ConcurrentDictionary<Type, AttributeItem[]> _cache = new();

    /// <summary>
    /// 获取组件的 AttributeItem 列表(带缓存)
    /// </summary>
    public static AttributeItem[] GetAttributes(Type componentType)
    {
        return _cache.GetOrAdd(componentType, type =>
        {
            var attributes = new List<AttributeItem>();
            var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                .Where(p => p.GetCustomAttribute<ParameterAttribute>() != null);

            foreach (var property in properties)
            {
                var item = new AttributeItem
                {
                    Name = property.Name,
                    Type = GetFriendlyTypeName(property.PropertyType),
                    Description = GetSummary(property) ?? "",
                    DefaultValue = GetDefaultValue(property) ?? "",
                    ValueList = GetValueList(property) ?? "",
                    Version = GetVersion(property) ?? ""
                };
                attributes.Add(item);
            }

            return attributes.ToArray();
        });
    }

    /// <summary>
    /// 从 XML 注释获取 summary
    /// </summary>
    private static string? GetSummary(PropertyInfo property)
    {
        var xmlDoc = GetXmlDocumentation(property.DeclaringType?.Assembly);
        if (xmlDoc == null) return null;

        var memberName = $"P:{property.DeclaringType?.FullName}.{property.Name}";
        var element = xmlDoc.Descendants("member")
            .FirstOrDefault(x => x.Attribute("name")?.Value == memberName);

        return element?.Element("summary")?.Value.Trim();
    }

    /// <summary>
    /// 从 XML 注释的 para version 节点获取版本信息
    /// </summary>
    private static string? GetVersion(PropertyInfo property)
    {
        var xmlDoc = GetXmlDocumentation(property.DeclaringType?.Assembly);
        if (xmlDoc == null) return null;

        var memberName = $"P:{property.DeclaringType?.FullName}.{property.Name}";
        var element = xmlDoc.Descendants("member")
            .FirstOrDefault(x => x.Attribute("name")?.Value == memberName);

        // 查找 <para><version>10.0.0</version></para>
        var versionElement = element?.Descendants("para")
            .SelectMany(p => p.Elements("version"))
            .FirstOrDefault();

        return versionElement?.Value.Trim();
    }

    /// <summary>
    /// 获取默认值
    /// </summary>
    private static string? GetDefaultValue(PropertyInfo property)
    {
        var defaultValueAttr = property.GetCustomAttribute<DefaultValueAttribute>();
        if (defaultValueAttr != null)
        {
            return defaultValueAttr.Value?.ToString() ?? "";
        }

        // 从 XML 注释中提取 DefaultValue
        var xmlDoc = GetXmlDocumentation(property.DeclaringType?.Assembly);
        if (xmlDoc == null) return null;

        var memberName = $"P:{property.DeclaringType?.FullName}.{property.Name}";
        var element = xmlDoc.Descendants("member")
            .FirstOrDefault(x => x.Attribute("name")?.Value == memberName);

        var defaultElement = element?.Element("value");
        return defaultElement?.Value.Trim();
    }

    /// <summary>
    /// 获取可选值列表
    /// </summary>
    private static string? GetValueList(PropertyInfo property)
    {
        // 如果是枚举类型,返回枚举值
        if (property.PropertyType.IsEnum)
        {
            return string.Join(" / ", Enum.GetNames(property.PropertyType));
        }

        return "";
    }

    /// <summary>
    /// 获取友好的类型名称
    /// </summary>
    private static string GetFriendlyTypeName(Type type)
    {
        if (type.IsGenericType)
        {
            var genericTypeName = type.GetGenericTypeDefinition().Name;
            genericTypeName = genericTypeName.Substring(0, genericTypeName.IndexOf('`'));
            var genericArgs = string.Join(", ", type.GetGenericArguments().Select(GetFriendlyTypeName));
            return $"{genericTypeName}<{genericArgs}>";
        }

        return type.Name switch
        {
            "Int32" => "int",
            "String" => "string",
            "Boolean" => "bool",
            "Double" => "double...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

*This pull request was created from Copilot chat.*
>

<!-- START COPILOT CODING AGENT TIPS -->
---

💬 We'd love your input! Share your thoughts on Copilot coding agent in our [2 minute survey](https://gh.io/copilot-coding-agent-survey).

@bb-auto
Copy link

bb-auto bot commented Jan 16, 2026

Thanks for your PR, @copilot. Someone from the team will get assigned to your PR shortly and we'll get it reviewed.

Copilot AI changed the title [WIP] Refactor Circles example component to use reflection for parameters Refactor Circles component to use reflection-based attribute extraction with caching Jan 16, 2026
Copilot AI requested a review from ArgoZhang January 16, 2026 07:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants