Biwen.QuickApi.Contents 2.0.5

dotnet add package Biwen.QuickApi.Contents --version 2.0.5
                    
NuGet\Install-Package Biwen.QuickApi.Contents -Version 2.0.5
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Biwen.QuickApi.Contents" Version="2.0.5" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Biwen.QuickApi.Contents" Version="2.0.5" />
                    
Directory.Packages.props
<PackageReference Include="Biwen.QuickApi.Contents" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Biwen.QuickApi.Contents --version 2.0.5
                    
#r "nuget: Biwen.QuickApi.Contents, 2.0.5"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#addin nuget:?package=Biwen.QuickApi.Contents&version=2.0.5
                    
Install Biwen.QuickApi.Contents as a Cake Addin
#tool nuget:?package=Biwen.QuickApi.Contents&version=2.0.5
                    
Install Biwen.QuickApi.Contents as a Cake Tool

Biwen.QuickApi.Contents

Biwen.QuickApi.Contents是基于Biwen.QuickApi用于内容管理的库,可以帮助您轻松地在项目中添加内容管理系统(CMS)功能,支持不同类型的内容字段、内容版本控制、审计和渲染。

功能特性

  • 灵活的内容模型定义
  • 多种字段类型支持(文本、数字、日期、图片、文件等)
  • 内容版本控制
  • 内容审计日志
  • 内容渲染服务
  • 支持内容草稿和发布工作流
  • 基于Elasticsearch的全文搜索功能

快速入门

1. 安装

在您的项目中引用Biwen.QuickApi.Contents包:

dotnet add package Biwen.QuickApi.Contents

2. 配置数据库上下文

创建一个实现IContentDbContext接口的数据库上下文:

public class YourDbContext : DbContext, IContentDbContext
{
    public YourDbContext(DbContextOptions<YourDbContext> options) : base(options)
    {
    }
    
    public DbSet<Content> Contents { get; set; }
    public DbSet<ContentAuditLog> ContentAuditLogs { get; set; }
    public DbSet<ContentVersion> ContentVersions { get; set; }
    
    public DbContext Context => this;
}

3. 在Program.cs中注册服务

// 配置Elasticsearch客户端(可选,如需使用搜索功能)
builder.Services.AddSingleton(sp =>
{
    return new ElasticsearchClient(new Uri(builder.Configuration["ElasticSearch"]!));
});
// 添加内容搜索服务(可选,如需使用搜索功能)
builder.Services.AddScoped<IContentSearchService, ElasticsearchService>();

// 添加BiweContents核心服务
builder.Services.AddBiwenContents<YourDbContext>(options => 
{
    // 是否启用Api接口
    options.EnableApi = true; // 默认启用
    // 是否生成OpenApi文档
    options.GenerateApiDocument = true; // 默认启用

    // 自定义配置
    options.ViewPath = "\\Views\\Contents"; // 设置内容视图路径
    options.PermissionValidator = httpContext => 
    {
        // 您的权限验证逻辑
        return Task.FromResult(httpContext.User.Identity!.IsAuthenticated);
    };
});

// 应用程序启动后初始化Elasticsearch索引(可选)
app.Lifetime.ApplicationStarted.Register(async () =>
{
    using var scope = app.Services.CreateScope();
    var searchService = scope.ServiceProvider.GetService<IContentSearchService>();
    if (searchService != null)
    {
        await searchService.InitializeIndexAsync();
    }
});

4. 定义内容模型

创建继承于ContentBase<T>的内容模型:

public class BlogPost : ContentBase<BlogPost>
{
    [Required]
    public TextFieldType Title { get; set; } = new();
    public UrlFieldType Slug { get; set; } = new();
    [MarkdownToolBar(MarkdownToolStyle.Standard)]
    public MarkdownFieldType Content { get; set; } = new();
    public DateTimeFieldType PublishDate { get; set; } = new();
    public ImageFieldType FeaturedImage { get; set; } = new();
    
    // 使用枚举类型作为选项
    public enum CategoryType
    {
        技术 = 1,
        新闻 = 2,
        生活 = 3
    }
    public OptionsFieldType<CategoryType> Category { get; set; } = new();
    
    // 多选项也使用枚举类型
    [Flags]
    public enum TagType
    {
        [Description(".NET")]
        DotNet = 1,
        CSharp = 2,
        AspNetCore = 4,
        Frontend = 8,
        Backend = 16
    }
    public OptionsMultiFieldType<TagType> Tags { get; set; } = new();
}

使用内容仓储

创建或更新内容

public class BlogService
{
    private readonly IContentRepository _contentRepository;
    
    public BlogService(IContentRepository contentRepository)
    {
        _contentRepository = contentRepository;
    }
    
    public async Task<Guid> CreateBlogPostAsync(BlogPost post)
    {
        // 创建内容并保存
        return await _contentRepository.SaveContentAsync(post, post.Title.Value, post.Slug.Value);
    }
    
    public async Task UpdateBlogPostAsync(Guid id, BlogPost post)
    {
        // 更新内容
        await _contentRepository.UpdateContentAsync(id, post);
    }
}

查询内容

// 通过ID获取内容
var post = await _contentRepository.GetContentAsync<BlogPost>(id);

// 获取特定类型的所有内容分页列表
var pagedPosts = await _contentRepository.GetContentsByTypeAsync<BlogPost>(pageIndex: 0, len: 10);

// 通过slug获取内容
var postBySlug = await _contentRepository.GetContentsByTypeAsync<BlogPost>("my-blog-post");

发布内容

// 设置内容状态为已发布
await _contentRepository.SetContentStatusAsync(id, ContentStatus.Published);

删除内容

await _contentRepository.DeleteContentAsync(id);

内容字段类型

Biwen.QuickApi.Contents支持以下字段类型:

  • TextFieldType - 短文本字段
  • TextAreaFieldType - 长文本字段
  • MarkdownFieldType - Markdown富文本
  • IntegerFieldType - 整数
  • NumberFieldType - 浮点数
  • BooleanFieldType - 布尔值
  • DateTimeFieldType - 日期时间
  • TimeFieldType - 时间
  • UrlFieldType - URL链接
  • ColorFieldType - 颜色
  • ImageFieldType - 图片
  • FileFieldType - 文件
  • OptionsFieldType<T> - 单选项(枚举)
  • OptionsMultiFieldType<T> - 多选项(枚举)
  • ArrayFieldType - 数组类型

内容渲染

您可以使用IDocumentRenderService来渲染内容:

public class BlogController : Controller
{
    private readonly IContentRepository _contentRepository;
    private readonly IDocumentRenderService _renderService;
    
    public BlogController(
        IContentRepository contentRepository, 
        IDocumentRenderService renderService)
    {
        _contentRepository = contentRepository;
        _renderService = renderService;
    }
    
    public async Task<IActionResult> ViewBlogPost(string slug)
    {
        var post = await _contentRepository.GetContentsByTypeAsync<BlogPost>(slug);
        if (post == null)
            return NotFound();
            
        // 渲染内容,使用指定的视图模板
        var renderedContent = await _renderService.RenderAsync(post, "BlogPost");
        
        return View("BlogPost", renderedContent);
    }
}

自定义文档类型视图

Biwen.QuickApi.Contents 允许您为每种内容类型创建自定义视图模板,以控制内容的呈现方式。

视图位置规范

视图文件应遵循以下命名和位置规范:

  1. 基础路径:视图文件默认位于 Views/Contents 目录下(可通过 BiwenContentOptions.ViewPath 配置)
  2. 视图命名:视图名称应与内容类型的简单名称匹配,例如 BlogPost.cshtml
  3. 子目录支持:您可以在 Views/Contents 下创建子目录来组织不同类别的视图

创建自定义视图模板

以下是创建自定义视图模板的步骤:

  1. 在您的项目中创建 Views/Contents 目录(如果不存在)
  2. 创建与您的内容类型名称相匹配的 .cshtml 文件,例如 BlogPost.cshtml
  3. 在视图文件中添加必要的命名空间和模型指令

示例视图模板 Views/Contents/BlogPost.cshtml

@model Biwen.QuickApi.Contents.Rendering.ContentViewModel<YourNamespace.BlogPost>
@{
    ViewData["Title"] = Model.Content.Title.Value;
    Layout = "_Layout"; // 使用您的布局文件
}

<div class="blog-post">
    <h1>@Model.Content.Title.Value</h1>
    
    <div class="metadata">
        <span class="date">发布于: @Model.Content.PublishDate.Value.ToString("yyyy-MM-dd")</span>
        <span class="category">分类: @Model.Content.Category.DisplayValue</span>
    </div>
    
    @if (Model.Content.FeaturedImage != null && !string.IsNullOrEmpty(Model.Content.FeaturedImage.Value))
    {
        <div class="featured-image">
            <img src="@Model.Content.FeaturedImage.Value" alt="@Model.Content.Title.Value" />
        </div>
    }
    
    <div class="content markdown-body">
        @Html.Raw(Model.Content.Content.Html)
    </div>
    
    @if (Model.Content.Tags != null && Model.Content.Tags.DisplayValues?.Any() == true)
    {
        <div class="tags">
            <strong>标签:</strong>
            @foreach (var tag in Model.Content.Tags.DisplayValues)
            {
                <span class="tag">@tag</span>
            }
        </div>
    }
</div>

视图模型结构

视图接收 ContentViewModel<T> 类型的模型,该模型包含两个主要属性:

  • Content:您的强类型内容实例(如 BlogPost
  • ContentDefine:内容的元数据信息(ID、创建时间、状态等)

访问字段值

在视图中访问内容字段值的方法:

  • 文本字段:Model.Content.Title.Value
  • Markdown字段:Html.Raw(Model.Content.Content.Html)(已转换为HTML)
  • 日期字段:Model.Content.PublishDate.Value.ToString("yyyy-MM-dd")
  • 选项字段(单选):Model.Content.Category.DisplayValue
  • 选项字段(多选):Model.Content.Tags.DisplayValues(字符串集合)

自定义CSS样式

为确保内容呈现一致,您可以引入特定的样式:

@section Styles {
    <link rel="stylesheet" href="~/css/markdown.css" />
    <link rel="stylesheet" href="~/css/blog-post.css" />
}

条件渲染

您可以根据内容的特定字段值进行条件渲染:

@if (Model.ContentDefine.Status == ContentStatus.Published)
{
    <div class="published-badge">已发布</div>
}
else if (Model.ContentDefine.Status == ContentStatus.Draft)
{
    <div class="draft-badge">草稿</div>
}

内容版本和审计

系统自动记录内容的变更历史:

// 获取内容的版本历史
var versions = await _contentVersionService.GetVersionsAsync(contentId);

// 查看内容审计日志
var auditLogs = await _contentAuditLogService.GetAuditLogsAsync(contentId);

权限控制

您可以通过配置BiwenContentOptions中的PermissionValidator来控制对内容管理功能的访问权限:

services.AddBiwenContents<YourDbContext>(options => 
{
    options.PermissionValidator = async httpContext => 
    {
        // 检查用户是否有管理员角色
        return httpContext.User.IsInRole("Admin");
    };
});

最佳实践

  1. 为每种内容类型创建专门的内容模型
  2. 使用内容版本控制来跟踪内容变更
  3. 为不同的内容类型创建专门的视图模板
  4. 利用内容状态工作流来管理发布流程
  5. 使用内容事件来实现自定义业务逻辑

内容搜索服务

Biwen.QuickApi.Contents提供基于Elasticsearch的内容搜索服务,支持全文搜索、分面搜索、高亮显示等高级功能。

自动索引更新

内容模块集成了自动索引更新功能,当内容被创建、更新、删除或状态发生变化时,系统会自动更新ElasticSearch索引。这是通过事件处理机制实现的,即使在应用程序没有启用ElasticSearch服务的情况下也不会产生错误。

事件处理器支持以下操作:

  • 当内容创建后,自动添加到索引
  • 当内容更新后,自动更新索引
  • 当内容删除后,自动从索引中移除
  • 当内容状态变更为已归档时,自动从索引中移除

配置Elasticsearch服务

  1. appsettings.json中添加Elasticsearch连接配置:
{
  "ElasticSearch": "http://localhost:9200"
}
  1. 在项目的启动配置中注册Elasticsearch客户端和搜索服务:
// 添加Elasticsearch客户端
builder.Services.AddSingleton(sp =>
{
    return new ElasticsearchClient(new Uri(builder.Configuration["ElasticSearch"]!));
});

// 添加内容搜索服务
builder.Services.AddScoped<IContentSearchService, ElasticsearchService>();
  1. 应用程序启动时初始化索引:
// 在应用程序启动时初始化Elasticsearch索引
app.UseEndpoints(endpoints =>
{
    // 其他端点配置...
    
    // 异步初始化Elasticsearch索引
    var scope = app.ApplicationServices.CreateScope();
    var searchService = scope.ServiceProvider.GetRequiredService<IContentSearchService>();
    _ = searchService.InitializeIndexAsync();
});

使用内容搜索服务

public class SearchController : Controller
{
    private readonly IContentSearchService _searchService;
    private readonly IContentRepository _contentRepository;
    
    public SearchController(IContentSearchService searchService, IContentRepository contentRepository)
    {
        _searchService = searchService;
        _contentRepository = contentRepository;
    }
    
    // 重建索引
    public async Task<IActionResult> RebuildIndex()
    {
        // 获取所有内容
        var allContents = await _contentRepository.GetAllContentsAsync();
        
        // 重建索引
        await _searchService.RebuildIndexAsync(allContents);
        return Ok("索引重建完成");
    }
    
    // 执行搜索
    public async Task<IActionResult> Search(string query, int page = 1, int size = 10, string filter = null, string sort = null)
    {
        // 执行搜索查询
        var results = await _searchService.SearchContentsAsync(
            query,
            page,
            size,
            filter,
            sort,
            enableHighlight: true,
            facets: ["contentType"] // 返回内容类型分面
        );
        
        return View(results);
    }
}

搜索功能特性

搜索服务支持以下功能:

  1. 全文搜索:支持跨字段的全文搜索,可以搜索内容标题、Slug以及内容字段的值。
  2. 模糊搜索:使用Elasticsearch的模糊匹配功能,容忍拼写错误。
  3. 高亮显示:自动高亮显示搜索结果中与查询相关的部分。
  4. 分面搜索:支持按内容类型、状态等属性进行分面聚合。
  5. 排序和过滤:支持按创建时间、更新时间、标题等字段排序,并支持多种过滤方式。

搜索查询语法

搜索服务支持多种查询和过滤语法:

  • 基础搜索:直接输入关键词,例如:博客
  • 类型过滤:按内容类型过滤,有两种格式:
    • contentType:BlogPost
    • contentType = 'BlogPost'
  • 状态过滤:按内容状态过滤,例如:status:Published
  • 字段筛选:按特定字段和值过滤,例如:field:Category=技术文章
  • 数值范围查询:按数值字段范围过滤,例如:range:Price=gte:100,lte:200
    • 支持的操作符:gte(大于等于)、lte(小于等于)、gt(大于)、lt(小于)
  • 日期范围查询:按日期字段范围过滤,例如:daterange:EventDate=gte:2024-01-01,lte:2024-12-31
    • 支持的操作符:gte(大于等于)、lte(小于等于)、gt(大于)、lt(小于)
  • 布尔条件查询:按布尔字段过滤,例如:bool:IsActive=true
  • 排序:支持多种排序选项:
    • createdAt:asc - 按创建时间升序
    • createdAt:desc - 按创建时间降序
    • updatedAt:asc - 按更新时间升序
    • updatedAt:desc - 按更新时间降序
    • title:asc - 按标题字母升序
    • title:desc - 按标题字母降序

嵌套字段查询示例

以下是一些嵌套字段查询的具体示例:

  1. 数值范围查询
// 查询价格在150到250之间的产品
var result = await _searchService.SearchContentsAsync("", filter: "range:Price=gte:150,lte:250");
  1. 日期范围查询
// 查询2024年上半年的活动
var result = await _searchService.SearchContentsAsync("", filter: "daterange:EventDate=gte:2024-01-01,lte:2024-06-30");
  1. 布尔条件查询
// 查询激活状态的产品
var result = await _searchService.SearchContentsAsync("", filter: "bool:IsActive=true");
  1. 组合查询
// 查询2024年上半年的激活状态产品,价格在100到200之间
var result = await _searchService.SearchContentsAsync("", 
    filter: "daterange:EventDate=gte:2024-01-01,lte:2024-06-30 AND bool:IsActive=true AND range:Price=gte:100,lte:200");

这些查询都支持嵌套字段,可以精确匹配 JsonContent 中的字段值。查询结果会自动过滤出符合所有条件的文档。

常见问题

Q: 如何自定义内容字段类型?

A: 您可以创建实现IFieldType接口的自定义字段类型,并在服务注册时添加它:

public class CustomFieldType : IFieldType
{
    // 实现接口方法
}

// 注册
services.AddSingleton<IFieldType, CustomFieldType>();

Q: 如何处理内容关联关系?

A: 您可以使用自定义字段类型来存储关联ID,或者在内容模型中使用特殊的引用字段。

Q: 如何优化Elasticsearch搜索性能?

A: 可以考虑以下几点:

  1. 在生产环境中增加分片数和副本数
  2. 为高频查询创建专用索引
  3. 使用筛选而非查询来提高性能
  4. 定期重建索引以优化存储

通过Slug访问内容

Biwen.QuickApi.Contents提供了一个便捷的方法,使您可以通过自定义URL格式访问内容。您可以在应用程序启动时配置这个功能:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // 其他中间件配置...
    
    app.UseEndpoints(endpoints =>
    {
        // 添加通过Slug访问内容的路由
        // 这将创建形如 /{prefix}/{slug} 的路由
        endpoints.MapBiwenContentsBySlug("p"); // 使用"p"作为URL前缀
        
        // 其他路由配置...
    });
}

上面的配置将创建如下格式的URL:/p/{slug},例如/p/my-blog-post。当用户访问这个URL时,系统会自动查找匹配的内容并使用IDocumentRenderService进行渲染。

您可以根据需要自定义前缀,例如使用"contents"、"articles"、"pages"等。

API 接口说明

Biwen.QuickApi.Contents 提供了一系列 API 接口,用于内容的增删改查等操作。所有 API 接口都位于 ~/contents 路径下(由 Constants.GroupName 定义)。

内容查询 API

GET ~~/contents/infopages

用于查询内容列表,支持分页和过滤。

查询参数:

  • pageNumber - 页码,从1开始,默认:1
  • pageSize - 分页大小,默认:10
  • contentType - 文档类型,必填
  • slug - Slug,如果提供则进行精确匹配
  • title - 按标题过滤
  • status - 状态过滤,0:草稿,1:已发布,2:归档

示例请求:

GET ~~/contents/infopages?contentType=BlogPost&pageNumber=1&pageSize=10&status=1

获取指定内容 API

GET ~~/contents/{id:guid}

根据 ID 获取指定的内容详情。

路径参数:

  • id - 内容的 GUID

示例请求:

GET ~~/contents/5f9c7b4e-3c1a-4f5b-9d1a-6c2a5e34b7d9

创建内容 API

POST ~~/contents/create

创建新的内容。

请求体:

{
  "title": "内容标题",
  "slug": "content-slug",
  "contentType": "BlogPost",
  "jsonContent": "[{\"fieldName\":\"Title\",\"value\":\"hello world\"}]"
}

字段说明:

  • title - 内容标题
  • slug - 内容的 URL 友好标识符
  • contentType - 内容类型的完全限定名或最后一个名称
  • jsonContent - 序列化的内容,为 JSON 字符串,格式为[{ "fieldName": "字段名称", "value": "字段值" }]

更新内容 API

PUT ~~/contents/update/{id:guid}

更新已有内容。

路径参数:

  • id - 内容的 GUID

请求体:

{
  "title": "更新后的标题",
  "slug": "updated-slug",
  "status": 1,
  "jsonContent": "更新后的序列化内容"
}

设置内容状态 API

PUT ~~/contents/status/{id:guid}

设置内容的状态,如发布或归档。

路径参数:

  • id - 内容的 GUID

请求体:

{
  "status": 1
}

状态值:

  • 0 - 草稿
  • 1 - 已发布
  • 2 - 归档

删除内容 API

DELETE ~~/contents/{id:guid}

删除指定的内容。

路径参数:

  • id - 内容的 GUID

预览内容 API

GET ~~/contents/preview/{id:guid}

预览指定内容,不改变内容状态。

路径参数:

  • id - 内容的 GUID

返回:

  • 返回HTML格式的内容预览

内容版本管理 API

获取内容版本列表
GET ~~/contents/versions/{id:guid}

获取指定内容的所有历史版本。

路径参数:

  • id - 内容的 GUID

返回:

  • 返回内容版本列表,包含版本ID、创建时间和版本说明等信息
获取指定版本内容
GET ~~/contents/versions/{id:guid}/{version:guid}

获取内容的特定历史版本。

路径参数:

  • id - 内容的 GUID
  • version - 版本的 GUID

返回:

  • 返回指定版本的内容快照
回滚内容版本
POST ~~/contents/versions/{id:guid}/rollback/{version:guid}

将内容回滚到指定的历史版本。

路径参数:

  • id - 内容的 GUID
  • version - 要回滚到的版本 GUID

返回:

  • 操作成功返回 true

内容审计日志 API

获取内容审计日志
GET ~~/contents/{id:guid}/auditlogs

获取指定内容的所有审计日志记录。

路径参数:

  • id - 内容的 GUID

返回:

  • 返回内容的所有审计日志记录,包含操作类型、操作时间和操作者等信息
按时间范围查询审计日志
GET ~~/contents/auditlogs

按时间范围查询系统中的内容审计日志。

查询参数:

  • startTime - 开始时间,默认为当天开始
  • endTime - 结束时间,默认为当前时间
  • pageNumber - 页码,从1开始
  • pageSize - 每页记录数

返回:

  • 返回符合条件的审计日志分页列表

更多资源

  • 查看示例项目以获取更多用法示例
  • 参考API文档获取详细信息
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
2.0.5 202 5/14/2025