New UWP Community Toolkit - Markdown

来源:https://www.cnblogs.com/shaomeng/archive/2018/03/30/8670758.html
-Advertisement-
Play Games

概述 前面 New UWP Community Toolkit 文章中,我們對 V2.2.0 版本的重要更新做了簡單回顧,其中簡單介紹了 MarkdownTextBlock 和 MarkdownDocument,本篇我們結合代碼詳細講解一下 Markdown 相關功能。 Markdown 是一種非常 ...


概述

前面 New UWP Community Toolkit 文章中,我們對 V2.2.0 版本的重要更新做了簡單回顧,其中簡單介紹了 MarkdownTextBlock 和 MarkdownDocument,本篇我們結合代碼詳細講解一下 Markdown 相關功能。

Markdown 是一種非常常用的標記語言,對於編寫文檔或者文章排版等有很大幫助:Markdown 維基百科。關於 Markdown 語法,大家可以去網路查詢,很容易上手,一次書寫,到各個平臺都能有一樣的操作體驗,非常的簡便實用。而 UWP Community Toolkit 對 Markdown 的解析和渲染提供了完整的支持,即使複雜的 Markdown 文本,也可以在低配置的硬體上獲得流暢的體驗。UWP Community Toolkit 完成 Markdown 整個功能的兩個重要組成部分就是:MarkdownTextBlock 和 MarkdownDocument。

MarkdownDocument 提供了對 markdown 的解析操作,傳遞給 MarkdownTextBlock,負責 markdown 解析後內容的渲染操作,然後顯示在界面。

MarkdownTextBlock 

Source: https://github.com/Microsoft/UWPCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock

Doc: https://docs.microsoft.com/zh-cn/windows/uwpcommunitytoolkit/controls/markdowntextblock

Namespace: Microsoft.Toolkit.Uwp.UI.Controls;  Nuget: Microsoft.Toolkit.Uwp.UI.Controls

 

MarkdownDocument 

Source: https://github.com/Microsoft/UWPCommunityToolkit/tree/master/Microsoft.Toolkit.Parsers/Markdown

Doc: https://docs.microsoft.com/zh-cn/windows/uwpcommunitytoolkit/parsers/markdownparser

Namespace: Microsoft.Toolkit.Parsers.Markdown;  Nuget: Microsoft.Toolkit.Parsers

 

代碼分析

MarkdownTextBlock

MarkdownTextBlock 項目起源自一個開源項目 - Universal Markdown: https://github.com/QuinnDamerell/UniversalMarkdown

Universal Markdown 是由 Quinn Damerell 和 Paul Bartrum 創建的開發項目,用於一個 reddit UWP 應用 Baconit。旨在創建一種通用的 markdown 渲染控制項,可以方便高效的使用。這個項目支持完整的 markdown 標記,性能表現也非常理想。

我們來看一下 MarkdownTextBlock 的項目結構:

  • Render 文件夾 - Markdown 實際渲染代碼
  • ***EventArgs.cs - Markdown 事件參數,比如超鏈接點擊時的鏈接地址參數
  • MarkdownTextBlock.Dimensions.cs - MarkdownTextBlock 部分類中負責設置各維度依賴屬性的類,包括字體、字型大小、背景色等的設置都由它負責
  • MarkdownTextBlock.Events.cs - MarkdownTextBlock 部分類中負責事件處理的類,包括鏈接點擊、圖片顯示等時間的觸發都由它負責
  • MarkdownTextBlock.Methods.cs - MarkdownTextBlock 部分類中負責具體方法執行的類,包括鏈接點擊、圖片顯示等方法的處理執行都由它負責
  • MarkdownTextBlock.Properties.cs - MarkdownTextBlock 部分類中負責設置和獲取各種屬性的類
  • MarkdownTextBlock.cs - MarkdownTextBlock 部分類,負責類初始化、主題變化響應等
  • MarkdownTextBlock.xaml - MarkdownTextBlock 類的 XAML 代碼,負責 UI 編寫和各種依賴屬性初始化

其中 Render 文件夾的項目結構:

  • ICodeBlockResolver.cs - 代碼塊渲染介面
  • IImageResolver.cs - 圖片渲染介面
  • ILinkRegister.cs - 鏈接註冊介面
  • InlineRenderContext - TextBlock 中的 Inline 集合渲染上下文
  • MarkdownRenderer.Blocks.cs - MarkdownRenderer 部分類中負責塊渲染的類,包括代碼、塊、段落、引用等的渲染由它負責
  • MarkdownRenderer.Dimensions.cs - MarkdownRenderer 部分類中負責獲取和設置各個維度量值的類
  • MarkdownRenderer.Inlines.cs - MarkdownRenderer 部分類中負責所有 Inline 渲染的類,包括常規、斜體、加粗、鏈接和圖片等
  • MarkdownRenderer.Properties.cs - MarkdownRenderer 部分類中負責獲取和設置所有屬性的類
  • MarkdownRenderer.cs - MarkdownRenderer 部分類負責初始化和渲染的類
  • MarkdownTable.cs - markdown 中表格控制項渲染類
  • RenderContext.cs - markdown 渲染上下文
  • RenderContextIncorrectException.cs - 渲染上下文不正確的異常定義類
  • UIElementCollectionRenderContext - UI 元素結合渲染上下文

接下來我們分幾個重要部分來詳細分析一下源代碼,因為篇幅考慮,我們只摘錄關鍵的代碼片段:

1. MarkdownTextBlock.Events.cs

可以看到,類為 MarkdownTextBlock 註冊了 MarkdownRendered、LinkClicked、ImageClicked、ImageResolving、CodeBlockResolving 這幾個事件,在渲染、點擊和需要顯示內容時使用;並相應兩種操作:Hyperlink_Click、NewImagelink_Tapped,分別是超鏈接點擊和圖片鏈接點按的操作處理,這也是 MarkdownTextBlock 僅有的兩種用戶主動觸發的事件。

private void Hyperlink_Click(Hyperlink sender, HyperlinkClickEventArgs args)
{
    LinkHandled((string)sender.GetValue(HyperlinkUrlProperty), true);
}

private void NewImagelink_Tapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e)
{
    LinkHandled((string)(sender as Image).GetValue(HyperlinkUrlProperty), false);
}

public event EventHandler<MarkdownRenderedEventArgs> MarkdownRendered;

public event EventHandler<LinkClickedEventArgs> LinkClicked;

public event EventHandler<LinkClickedEventArgs> ImageClicked;

public event EventHandler<ImageResolvingEventArgs> ImageResolving;

public event EventHandler<CodeBlockResolvingEventArgs> CodeBlockResolving;

2. MarkdownTextBlock.Methods.cs

我們截取了幾個重要的方法:

  • RenderMarkdown() - 使用 MarkdownDocument 類解析文本,然後使用上面所述 Render 文件夾中的 MarkdownRender 來渲染,添加到父容器中;
  • RegisterNewHyperLink(s,e) -  註冊一個新的超鏈接,在點擊操作時觸發這個事件;超鏈接和圖片鏈接都會被註冊;
  • ICodeBlockResolver.ParseSyntax(a,b,c) - 解析代碼塊的語法,如果沒有複製,則根據系統主題和富文本控制項的預設樣式初始化一個值
private void RenderMarkdown()
{
    // Try to parse the markdown.
    MarkdownDocument markdown = new MarkdownDocument();
    markdown.Parse(Text);

    // Now try to display it
    var renderer = Activator.CreateInstance(renderertype, markdown, this, this, this) as MarkdownRenderer;
    // set properties
    ...
    _rootElement.Child = renderer.Render();
    // Indicate that the parse is done.
    MarkdownRendered?.Invoke(this, markdownRenderedArgs);
}

public void RegisterNewHyperLink(Hyperlink newHyperlink, string linkUrl)
{
    // Setup a listener for clicks.
    newHyperlink.Click += Hyperlink_Click;

    // Associate the URL with the hyperlink.
    newHyperlink.SetValue(HyperlinkUrlProperty, linkUrl);

    // Add it to our list
    _listeningHyperlinks.Add(newHyperlink);
}
bool ICodeBlockResolver.ParseSyntax(InlineCollection inlineCollection, string text, string codeLanguage)
{
    ...
    if (language != null)
    {
        RichTextBlockFormatter formatter;
        if (CodeStyling != null)
        {
            formatter = new RichTextBlockFormatter(CodeStyling);
        }
        else
        {
            var theme = themeListener.CurrentTheme == ApplicationTheme.Dark ? ElementTheme.Dark : ElementTheme.Light;
            if (RequestedTheme != ElementTheme.Default)
            {
                theme = RequestedTheme;
            }
            formatter = new RichTextBlockFormatter(theme);
        }
        formatter.FormatInlines(text, language, inlineCollection);
    }
    ...
}

3. MarkdownRenderer.Blocks.cs

我們省略了大部分方法的實現過程,主要讓大家看到都有哦哪些類型的渲染,而他們和 RenderParagraph 都比較相似;大致的實現過程就是讀取解析後的 element,讀取對應的 margin width thickness 等信息來初始化控制項,然後把控制項以配置的某個位置和尺寸添加到 TextBlock 中,渲染到 UI 中。

protected override void RenderBlocks(IEnumerable<MarkdownBlock> blockElements, IRenderContext context) {...}

protected override void RenderParagraph(ParagraphBlock element, IRenderContext context)
{
    var paragraph = new Paragraph
    {
        Margin = ParagraphMargin
    };

    var childContext = new InlineRenderContext(paragraph.Inlines, context)
    {
        Parent = paragraph
    };

    RenderInlineChildren(element.Inlines, childContext);

    var textBlock = CreateOrReuseRichTextBlock(context);
    textBlock.Blocks.Add(paragraph);
}

protected override void RenderHeader(HeaderBlock element, IRenderContext context) {...}

protected override void RenderListElement(ListBlock element, IRenderContext context) {...}

protected override void RenderHorizontalRule(IRenderContext context) {...}

protected override void RenderQuote(QuoteBlock element, IRenderContext context) {...}

protected override void RenderCode(CodeBlock element, IRenderContext context) {...}

protected override void RenderTable(TableBlock element, IRenderContext context) {...}

4. MarkdownRenderer.Inlines.cs

我們同樣省略了大部分方法的實現過程,主要看都有哪些渲染的類型,包括表情、粗體、斜體、超鏈接、圖片、上標和代碼等;參照 Emoji 的實現過程,讀取 inline 中的 Emoji,設置文字信息和 Emoji 內容,然後添加到 inline 集合中。

protected override void RenderEmoji(EmojiInline element, IRenderContext context)
{
    var localContext = context as InlineRenderContext;
    ...
    var inlineCollection = localContext.InlineCollection;
    var emoji = new Run
    {
        FontFamily = EmojiFontFamily ?? DefaultEmojiFont,
        Text = element.Text
    };
    inlineCollection.Add(emoji);
}

protected override void RenderTextRun(TextRunInline element, IRenderContext context) {...}

protected override void RenderBoldRun(BoldTextInline element, IRenderContext context) {...}

protected override void RenderMarkdownLink(MarkdownLinkInline element, IRenderContext context) {...}

protected override void RenderHyperlink(HyperlinkInline element, IRenderContext context) {...}

protected override async void RenderImage(ImageInline element, IRenderContext context) {...}

protected override void RenderItalicRun(ItalicTextInline element, IRenderContext context) {...}

protected override void RenderStrikethroughRun(StrikethroughTextInline element, IRenderContext context) {...}

protected override void RenderSuperscriptRun(SuperscriptTextInline element, IRenderContext context) {...}

protected override void RenderCodeRun(CodeInline element, IRenderContext context) {...}

5. MarkdownRenderer.cs

我們來看,渲染器初始化時,傳入的是鏈接註冊、圖片顯示、代碼塊顯示和表情字體(預設為 Segoe UI Emoji);後面提供了創建文本、創建富文本的方法,以及修改某個範圍內的 runs,檢測是否上標、去掉上標等方法;

public MarkdownRenderer(MarkdownDocument document, ILinkRegister linkRegister, IImageResolver imageResolver, ICodeBlockResolver codeBlockResolver)
: base(document)
{
    LinkRegister = linkRegister;
    ImageResolver = imageResolver;
    CodeBlockResolver = codeBlockResolver;
    DefaultEmojiFont = new FontFamily("Segoe UI Emoji");
}

protected RichTextBlock CreateOrReuseRichTextBlock(IRenderContext context) {...}

protected TextBlock CreateTextBlock(RenderContext context) {...}

protected void AlterChildRuns(Span parentSpan, Action<Span, Run> action) {...}

private bool AllTextIsSuperscript(IInlineContainer container, int superscriptLevel = 0) {...}

private void RemoveSuperscriptRuns(IInlineContainer container, bool insertCaret) {...}

調用示例:

看完源代碼的主要構成後,我們再簡單看一下 MarkdownTextBlock 的使用過程:

我們在其中添加了正常顯示文本、粗體和斜體,還添加了超鏈接文本,而在 LinkClicked 事件中處理超鏈接的跳轉。在複雜的源代碼之上,使用過程變得非常簡單,我們只需要準備好 markdown 文本,以及需要處理的點擊、點按等事件就可以了。

<controls:MarkdownTextBlock
    Text="This control was originally written by [Quinn Damerell](https://github.com/QuinnDamerell) 
            and [Paul Bartrum](https://github.com/paulbartrum) for [Baconit](https://github.com/QuinnDamerell/Baconit), 
            a popular open source reddit UWP. The control *almost* supports the full markdown syntax, with a focus on super-efficient 
            parsing and rendering. The control is efficient enough to be used in virtualizing lists.
            *Note:* For a full list of markdown syntax, see the [official syntax guide](http://daringfireball.net/projects/markdown/syntax).
            **Try it live!** Type in the *unformatted text box* above!"
    LinkClicked="MarkdownText_LinkClicked"
    Margin="6">
</controls:MarkdownTextBlock>

 

MarkdownDocument

MarkdownDocument 是 Markdown Parser 的主要組成部分,負責 markdown 文本的解析工作,把文本解析為 MarkdownDocument,而 Markdown Parser 還提供了 MarkdownRendererBase,作為渲染功能的基類,它也是 MarkdownTextBlock 的 MarkdownRenderer.cs 類的基類。

來看一下 Markdown Parser 的項目主要構成:

  • Blocks - 每個分類塊的解析類
  • Enums - 各個類型的枚舉類
  • Helpers - 一些通用的幫助類
  • Inlines - TextBlock 中 inline 解析類
  • Render - Markdown Parser 負責渲染的基類
  • MarkdownBlock.cs - Markdown 塊定義類, MarkdownDocument 的基類
  • MarkdownDocument.cs - Markdown Parser 和 Render 的主要類
  • MarkdownElement.cs - 所有 Markdown 元素的基類
  • MarkdownInline.cs - markdown inline 元素的基類

接下來我們分幾個重要部分來詳細分析一下源代碼,因為篇幅考慮,我們只摘錄關鍵的代碼片段:

1. MarkdownDocument.cs

 MarkdownDocument 負責 markdown parser 的主要功能,看到兩個變數:_references 存放鏈接和對應文本的列表,Blocks 存放文本,包含樣式;public 的 Parse 方法複雜解析和整理文本/鏈接文本;internal 的 Parse 方法負責實際的解析工作,按照 MarkdownBlock 的類型分別解析每種 Block,拆分每個特殊符號,根據 Block 的換行/縮進等屬性進行單獨的解析工作;LookUpReference 方法負責查找引用的 ID;

private Dictionary<string, LinkReferenceBlock> _references;
public IList<MarkdownBlock> Blocks { get; set; }

public void Parse(string markdownText)
{
    int actualEnd;
    Blocks = Parse(markdownText, 0, markdownText.Length, quoteDepth: 0, actualEnd: out actualEnd);

    // Remove any references from the list of blocks, and add them to a dictionary.
    for (int i = Blocks.Count - 1; i >= 0; i--)
    {
        if (Blocks[i].Type == MarkdownBlockType.LinkReference)
        {
            var reference = (LinkReferenceBlock)Blocks[i];
            if (_references == null)
            {
                _references = new Dictionary<string, LinkReferenceBlock>(StringComparer.OrdinalIgnoreCase);
            }

            if (!_references.ContainsKey(reference.Id))
            {
                _references.Add(reference.Id, reference);
            }

            Blocks.RemoveAt(i);
        }
    }
}

internal static List<MarkdownBlock> Parse(string markdown, int start, int end, int quoteDepth, out int actualEnd) 
{
    // We need to parse out the list of blocks.
    // Some blocks need to start on a new paragraph (code, lists and tables) while other
    // blocks can start on any line (headers, horizontal rules and quotes).
    // Text that is outside of any other block becomes a paragraph.
    var blocks = new List<MarkdownBlock>();
    int startOfLine = start;
    bool lineStartsNewParagraph = true;
    var paragraphText = new StringBuilder();

    // These are needed to parse underline-style header blocks.
    int previousStartOfLine = start;
    int previousEndOfLine = start;

    // Go line by line.
    while (startOfLine < end)
    {
        // Parse all kinds of blocks
        ...
    }
    actualEnd = startOfLine;
    return blocks;
}

public LinkReferenceBlock LookUpReference(string id) {...}

2. Render / MarkdownRendererBase.cs

前面我們說到, MarkdownTextBlock 的 Render 功能繼承自 MarkdownRendererBase 類。這個類定義了每種不同類型的 Block 和 Inline 的渲染;我們看到兩個主要方法:RenderBlock 和 RenderInline,根據不同的類型,分別進行渲染。

我們在實現 Renderer 功能的時候,可以繼承 MarkdownRendererBase 類,像 MarkdownTextBlock 那樣,也可以根據自己的需求,做一些類型的定製化。

public virtual void Render(IRenderContext context)
{
    RenderBlocks(Document.Blocks, context);
}

protected virtual void RenderBlocks(IEnumerable<MarkdownBlock> blockElements, IRenderContext context)
{
    foreach (MarkdownBlock element in blockElements)
    {
        RenderBlock(element, context);
    }
}

protected void RenderBlock(MarkdownBlock element, IRenderContext context)
{
    {
        switch (element.Type)
        {
            case MarkdownBlockType.Paragraph:
                RenderParagraph((ParagraphBlock)element, context);
                break;
            // case other Block types
            ...
        }
    }
}

protected void RenderInline(MarkdownInline element, IRenderContext context)
{
    switch (element.Type)
    {
        case MarkdownInlineType.TextRun:
            RenderTextRun((TextRunInline)element, context);
            break;
        // case other Inline types
        ...
    }
}        

3. Blocks / CodeBlock.cs

 上面的 MarkdownDocument 類中涉及到每種類型的 Parse 功能,而實際的 Parse 工作由每個 Block 和 Inline 完成,我們在 Block 中用 CodeBlock 做例子,可以看到 Parse 方法會把對應的 markdown 文本解析為 Renderer 可以識別的元素;

internal static CodeBlock Parse(string markdown, int start, int maxEnd, int quoteDepth, out int actualEnd)
{
    StringBuilder code = null;
    actualEnd = start;
    bool insideCodeBlock = false;
    string codeLanguage = string.Empty;

    /*
        Two options here:
        Either every line starts with a tab character or at least 4 spaces
        Or the code block starts and ends with ```
    */
    foreach (var lineInfo in Common.ParseLines(markdown, start, maxEnd, quoteDepth))
    {
        ...
    }
    ...
}

調用示例:

一段簡單 markdown 字元串(This is Markdown)的解析代碼和結果:

This is 和 Markdown 被解析為兩個 Inline,Type = 'TextRun',其中 Markdown 的 顯示 Type = 'Bold',這個預期的一致,Markdown 顯示為加粗。

string md = "This is **Markdown**";
MarkdownDocument Document = new MarkdownDocument();
Document.Parse(md);

// Takes note of all of the Top Level Headers.
foreach (var element in document.Blocks)
{
    if (element is HeaderBlock header)
    {
        Console.WriteLine($"Header: {header.ToString()}");
    }
}

 

總結

到這裡我們就把 UWP Community Toolkit 中的 Markdown 功能的源代碼實現過程和簡單的調用示例講解完成了。源代碼的實現功能點很多很強大,對於理解 markdown 的規則和 markdown 與 UWP XAML 的轉換都非常有幫助,而最終的調用非常簡單易用,真的要感謝 CommunityToolkit 的作者們。

如果大家有興趣,或想開發 Markdown 相關的功能,可以對源代碼和調用做更深入的研究,歡迎大家多多交流,謝謝!

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 記錄日誌時, 經常需要描述對象的狀態發生了怎樣的變化, 以前處理的非常簡單粗暴: a. 重寫class的ToString()方法, 將重要的屬性都輸出來 b. 記錄日誌時: 誰誰誰 由 變更前實例.ToString() 變成 變更後實例.ToString() 但輸出的日誌總是太長了, 翻看日誌時想找 ...
  • RabbitMQ是一種重要的消息隊列中間件,在生產環境中,穩定是第一考慮。RabbitMQ廠家也深知開發者的聲音,穩定、可靠是第一考慮,為了消息傳輸的可靠性傳輸,RabbitMQ提供了多種途徑的消息持久化保證:Exchange持久化、Queue持久化及Message的持久化等。以保證RabbitMQ ...
  • c yield關鍵字的用法 1.yield實現的功能 yield return: 先看下麵的代碼,通過yield return實現了類似用foreach遍曆數組的功能,說明yield return也是用來實現迭代器的功能的。 <! more yield break: 再看下麵的代碼,只輸出了1,2, ...
  • l GridView無代碼分頁排序 l GridView選中,編輯,取消,刪除 l GridView正反雙向排序 l GridView和下拉菜單DropDownList結合 l GridView和CheckBox結合 l 滑鼠移到GridView某一行時改變該行的背景色方法一 l 滑鼠移到GridV ...
  • 公司是做CS產品的, 最近分配給我一個活, 要求: 1. 公司程式啟動時, 檢測是否有配置文件, 沒有的話則按預設值創建一個 2. 配置文件要加密, 不能讓客戶隨便看到裡面的參數 3. 配置文件要有配套的GUI配置工具, 因為現場實施人員嫌XML配置麻煩 如果只有一個產品需要這個功能, 把每個配置項 ...
  • 1.運算符 (1)分類 算術運算符、關係運算符、邏輯運算符、位運算符、賦值運算符、其他運算符 >.算術運算符: >.關係運算符: using System; namespace study { public class demo0_operator { static void Main(string ...
  • 1、安裝組件 Microsoft.AspNet.Identity.Core,身份認證核心組件 安裝Microsoft.AspNet.Identity.EntityFramework,EF實現身份認證 安裝Microsoft.AspNet.Identity.OWIN,身份認證的OWIN插件,用於替代F ...
  • //字元串轉bytes var ebytes = System.Text.Encoding.Default.GetBytes(keyWord); //bytes進行base64加密 var strBase64 = Convert.ToBase64String(ebytes); //base64字元轉 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...