using System.Text.Json.Serialization;
using System.Xml.Serialization;
using Dpz.Core.Entity.Base.MapperConfig;
using Dpz.Core.Public.Entity.Seo;
using Mapster;

namespace Dpz.Core.Public.ViewModel;

/// <summary>
/// Represents the VmPageMetadata type.
/// </summary>
public class VmPageMetadata : IHaveCustomMapping
{
    /// <summary>
    /// Gets or sets Id.
    /// </summary>
    public string? Id { get; set; }

    /// <summary>
    /// 标准化关联路径(小写)
    /// </summary>
    public string? RelationPath { get; set; }

    /// <summary>
    /// 是否公共元数据。
    /// </summary>
    public bool IsPublicMetadata { get; set; }

    /// <summary>
    /// 公共元数据是否应用到没有显式配置的页面。
    /// </summary>
    public bool ApplyToUnconfiguredPages { get; set; }

    /// <summary>
    /// 页面元数据继承方式。
    /// </summary>
    public PageMetadataInheritanceMode InheritanceMode { get; set; } =
        PageMetadataInheritanceMode.Inherit;

    /// <summary>
    /// 结构化路由关联。
    /// </summary>
    public PageMetadataRoute? Route { get; set; }

    /// <summary>
    /// 标准化路由键。
    /// </summary>
    public string? RouteKey { get; set; }

    /// <summary>
    /// 非必要不要设置
    /// </summary>
    public string? Title { get; set; }

    /// <summary>
    /// 关键字
    /// </summary>
    public List<string> Keywords { get; set; } = [];

    /// <summary>
    /// 描述
    /// </summary>
    public string? Description { get; set; }

    /// <summary>
    /// 其它元数据
    /// </summary>
    [XmlIgnore]
    public Dictionary<string, string> Metas { get; set; } = [];

    /// <summary>
    /// Gets or sets SerializableMetas.
    /// </summary>
    [XmlArray(ElementName = "Metas")]
    [XmlArrayItem(ElementName = "Meta")]
    [JsonIgnore]
    public KeyValueElement[] SerializableMetas =>
        Metas.Select(x => new KeyValueElement(x.Key, x.Value)).ToArray();

    /// <summary>
    /// 关联,唯一 {controller,action,id...}。仅用于兼容旧数据。
    /// </summary>
    public List<string> Relations { get; set; } = [];

    /// <summary>
    /// Executes CreateMappings.
    /// </summary>
    public static void CreateMappings(TypeAdapterConfig cfg)
    {
        cfg.NewConfig<PageMetadata, VmPageMetadata>().MapWith(x => ToViewModel(x));
        cfg.NewConfig<VmPageMetadata, PageMetadata>().MapWith(x => ToEntity(x));
    }

    private static VmPageMetadata ToViewModel(PageMetadata source)
    {
        return new VmPageMetadata
        {
            Id = source.Id.ToString(),
            RelationPath = source.RelationPath,
            IsPublicMetadata = source.IsPublicMetadata,
            ApplyToUnconfiguredPages = source.ApplyToUnconfiguredPages,
            InheritanceMode = source.InheritanceMode,
            Route = source.Route,
            RouteKey = source.RouteKey,
            Title = source.Title,
            Keywords = source.Keywords,
            Description = source.Description,
            Metas = source.Metas,
            Relations = source.Relations,
        };
    }

    private static PageMetadata ToEntity(VmPageMetadata source)
    {
        return new PageMetadata
        {
            IsPublicMetadata = source.IsPublicMetadata,
            ApplyToUnconfiguredPages = source.ApplyToUnconfiguredPages,
            InheritanceMode = source.InheritanceMode,
            Route = source.Route,
            RouteKey = source.RouteKey,
            Title = source.Title,
            Keywords = source.Keywords,
            Description = source.Description,
            Metas = source.Metas,
            Relations = source.Relations,
        };
    }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

文件概览

VmPageMetadata.cs 是 SEO「页面元数据」体系的对外视图模型(ViewModel / DTO),位于 Dpz.Core.Public.ViewModel 命名空间。它把持久化实体 PageMetadata.cs 的数据以适合 API 传输、视图渲染和 XML 输出的形式暴露出来,同时承担实体与 DTO 之间的双向映射定义。

该类实现了 IHaveCustomMapping(定义见 IHaveCustomMapping.cs),该接口只有一个 static abstract void CreateMappings(TypeAdapterConfig cfg),属于 Mapster 的“约定式映射注册”入口。


成员职责

1. 数据字段(与实体一一对应)

以下字段与实体 PageMetadata 同名同义,用于在管理端/渲染端传递 SEO 配置:

  • Id:这里声明为 string?,而实体继承的 BaseEntity 使用的是对象 Id 类型(ToViewModel 里以 source.Id.ToString() 转换,ToEntity 时不回填)。
  • RelationPath:标准化关联路径(小写)。注意实体里的 RelationPath 是 [BsonIgnore] 的只读计算属性(由 RouteKey 或 Relations 推导),而 ViewModel 中是可读写的普通属性,映射时由 ToViewModel 显式复制。
  • IsPublicMetadata、ApplyToUnconfiguredPages、InheritanceMode(默认 Inherit,枚举措见 PageMetadataInheritanceMode.cs):控制是否作为“公共元数据”、是否应用到未显式配置的页面,以及继承策略。
  • Route(PageMetadataRoute.cs)与 RouteKey:结构化路由关联及其标准化键(由 PageMetadataRoute.BuildRouteKey() 生成)。
  • Title、Keywords、Description、Metas:面向 SEO 的标题、关键字、描述与自定义 meta 键值集合。
  • Relations:{controller,action,id...} 形式的旧数据关联列表,注释明确说明仅用于兼容旧数据。

2. 序列化适配

  • Metas 上标注 [XmlIgnore],因为 Dictionary<string,string> 不能直接被 XML 序列化。
  • SerializableMetas 是一个只读投影属性:用 LINQ 把 Metas 转成 KeyValueElement[](KeyValueElement.cs),并标注 [XmlArray(ElementName = "Metas")] / [XmlArrayItem(ElementName = "Meta")] 以适配 XML 输出,同时 [JsonIgnore] 避免在 JSON 中重复输出。

    说明:在可检索到的源码中,仅在该文件内部出现了 SerializableMetas 的定义,未发现其他调用/引用点,因此其消费方(例如 XML 序列化端点)我无法从现有检索结果确认。

3. 映射定义(静态方法)

  • CreateMappings:向 TypeAdapterConfig 注册 PageMetadata → VmPageMetadata 和 VmPageMetadata → PageMetadata 两个方向的映射,都使用 MapWith 委托而非自动成员匹配。
  • ToViewModel:显式逐字段投影,做出两处“不直接拷贝”的处理——Id 转字符串、RelationPath 取自实体的计算属性。
  • ToEntity:反向投影,不写入 Id、RelationPath、RouteKey;RouteKey 交由服务层规范化流程生成(见下文协作)。

与相关文件的协作

  • 服务层 PageMetadataService.cs 以 VmPageMetadata 作为查询/保存的出入参类型:GetPageMetadataAsync、GetPublicMetadataAsync、GetByIdAsync、GetPageAsync、GetPageMetadataByRouteAsync、ResolvePageMetadataAsync 等方法通过 mapper.Map<VmPageMetadata>(entity) 产出该 DTO;SavePageMetadataAsync/NormalizePageMetadata/Merge 则在 DTO 上做校验、规范化(计算 RouteKey、清空公共元数据的 Route/Relations 等)与多来源合并。
  • 管理接口 SeoController.cs 直接用 VmPageMetadata 组装保存请求、声明返回值类型,并转发给 IPageMetadataService。
  • MVC 渲染链路:控制器(如 ArticleController.cs、CodeController.cs、AlbumController.cs、SteamController.cs)构造 VmPageMetadata 作为“显式元数据”放入 ViewData["PageMetadata"],由 GlobalFilter.cs 调用 ResolvePageMetadataAs... 解析合并,最终渲染到 _PageMetadataPartial.cshtml。
  • 预览 DTO SeoPreviewDto.cs 以 VmPageMetadata? 作为可选的 ExplicitMetadata 字段类型。

映射与调用关系示意:

flowchart LR
    E["PageMetadata 实体<br/>(Entity/Seo)"]
    VM["VmPageMetadata<br/>(本文件)"]
    SVC["PageMetadataService"]
    WEB["SeoController / WebApi"]
    MVC["MVC 控制器 + GlobalFilter<br/>_PageMetadataPartial.cshtml"]

    E -- "MapWith: ToViewModel" --> VM
    VM -- "MapWith: ToEntity" --> E
    SVC -- "mapper.Map 返回/接收" --> VM
    WEB -- "请求/响应模型" --> VM
    MVC -- "ViewData 显式元数据" --> VM
    SVC -- "ResolvePageMetadataAsync 合并" --> VM

小结

VmPageMetadata 是 SEO 页面元数据在应用层与外部(Web API、MVC 视图、XML 输出)之间的统一数据契约:字段布局镜像 PageMetadata 实体,Id/RelationPath 等做了传输友好的差异处理;通过 IHaveCustomMapping 静态注册双向显式映射,避免依赖自动约定;并通过 SerializableMetas 桥接不适合 XML 序列化的字典结构。其真正的业务语义(继承/合并、路由匹配、公共元数据规则、缓存失效)都在 PageMetadataService.cs 中实现,本文件更多是“形状与映射”的定义者。

需要提醒的点:ToEntity 不还原 Id 与 RouteKey,这意味着从 DTO 反向生成实体后,路由键依赖服务层 NormalizePageMetadata 补齐;SerializableMetas 目前未在可检索范围内发现调用方,若后续要用于 XML 输出需确认对应序列化入口。

评论加载中...