ASP.NET Core 中間件(Middleware)的使用及其源碼解析(一)

来源:https://www.cnblogs.com/xyh9039/archive/2022/04/17/16146620.html
-Advertisement-
Play Games

本章將和大家分享ASP.NET Core 中間件(Middleware)的使用及其源碼解析。 ...


中間件是一種裝配到應用管道以處理請求和響應的軟體。每個組件:

1、選擇是否將請求傳遞到管道中的下一個組件。

2、可在管道中的下一個組件前後執行工作。

請求委托用於生成請求管道。請求委托處理每個 HTTP 請求。

請求管道中的每個中間件組件負責調用管道中的下一個組件,或使管道短路。當中間件短路時,它被稱為“終端中間件”,因為它阻止中間件進一步處理請求。

廢話不多說,我們直接來看一個Demo,Demo的目錄結構如下所示:

本Demo的Web項目為ASP.NET Core Web 應用程式(目標框架為.NET Core 3.1) MVC項目。  

其中 Home 控制器代碼如下:

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace NETCoreMiddleware.Controllers
{
    public class HomeController : Controller
    {
        private readonly ILogger<HomeController> _logger;

        public HomeController(ILogger<HomeController> logger)
        {
            _logger = logger;
        }

        public IActionResult Index()
        {
            Console.WriteLine("");
            Console.WriteLine($"This is {typeof(HomeController)} Index");
            Console.WriteLine("");
            return View();
        }
    }
}

其中 Startup.cs 類的代碼如下:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace NETCoreMiddleware
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        //服務註冊(往容器中添加服務)
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
        }

        /// <summary>
        /// 配置Http請求處理管道
        /// Http請求管道模型---就是Http請求被處理的步驟
        /// 所謂管道,就是拿著HttpContext,經過多個步驟的加工,生成Response,這就是管道
        /// </summary>
        /// <param name="app"></param>
        /// <param name="env"></param>
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            #region 環境參數

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }

            #endregion 環境參數

            //靜態文件中間件
            app.UseStaticFiles();

            #region Use中間件

            //中間件1
            app.Use(next =>
            {
                Console.WriteLine("middleware 1");
                return async context =>
                {
                    await Task.Run(() =>
                    {
                        Console.WriteLine("");
                        Console.WriteLine("===================================Middleware===================================");
                        Console.WriteLine($"This is middleware 1 Start");
                    });
                    await next.Invoke(context);
                    await Task.Run(() =>
                    {
                        Console.WriteLine($"This is middleware 1 End");
                        Console.WriteLine("===================================Middleware===================================");
                    });
                };
            });

            //中間件2
            app.Use(next =>
            {
                Console.WriteLine("middleware 2");
                return async context =>
                {
                    await Task.Run(() => Console.WriteLine($"This is middleware 2 Start"));
                    await next.Invoke(context); //可通過不調用 next 參數使請求管道短路
                    await Task.Run(() => Console.WriteLine($"This is middleware 2 End"));
                };
            });

            //中間件3
            app.Use(next =>
            {
                Console.WriteLine("middleware 3");
                return async context =>
                {
                    await Task.Run(() => Console.WriteLine($"This is middleware 3 Start"));
                    await next.Invoke(context);
                    await Task.Run(() => Console.WriteLine($"This is middleware 3 End"));
                };
            });

            #endregion Use中間件

            #region 最終把請求交給MVC

            app.UseRouting();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "areas",
                    pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");

                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
            });

            #endregion 最終把請求交給MVC
        }
    }
}

用 Use 將多個請求委托鏈接在一起,next 參數表示管道中的下一個委托(下一個中間件)。

下麵我們使用命令行(CLI)方式啟動我們的網站,如下所示:

可以發現控制台依次輸出了“middleware 3” 、“middleware 2”、“middleware 1”,這是怎麼回事呢?此處我們先留個疑問,該點在後面的講解中會再次提到。

啟動成功後,我們來訪問一下 “/home/index” ,控制台輸出結果如下所示:

請求管道包含一系列請求委托,依次調用,下圖演示了這一過程:

每個委托均可在下一個委托前後執行操作。應儘早在管道中調用異常處理委托,這樣它們就能捕獲在管道的後期階段發生的異常。

此外,可通過不調用 next 參數使請求管道短路,如下所示:

/// <summary>
/// 配置Http請求處理管道
/// Http請求管道模型---就是Http請求被處理的步驟
/// 所謂管道,就是拿著HttpContext,經過多個步驟的加工,生成Response,這就是管道
/// </summary>
/// <param name="app"></param>
/// <param name="env"></param>
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    #region 環境參數

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    #endregion 環境參數

    //靜態文件中間件
    app.UseStaticFiles();

    #region Use中間件

    //中間件1
    app.Use(next =>
    {
        Console.WriteLine("middleware 1");
        return async context =>
        {
            await Task.Run(() =>
            {
                Console.WriteLine("");
                Console.WriteLine("===================================Middleware===================================");
                Console.WriteLine($"This is middleware 1 Start");
            });
            await next.Invoke(context);
            await Task.Run(() =>
            {
                Console.WriteLine($"This is middleware 1 End");
                Console.WriteLine("===================================Middleware===================================");
            });
        };
    });

    //中間件2
    app.Use(next =>
    {
        Console.WriteLine("middleware 2");
        return async context =>
        {
            await Task.Run(() => Console.WriteLine($"This is middleware 2 Start"));
            //await next.Invoke(context); //可通過不調用 next 參數使請求管道短路
            await Task.Run(() => Console.WriteLine($"This is middleware 2 End"));
        };
    });

    //中間件3
    app.Use(next =>
    {
        Console.WriteLine("middleware 3");
        return async context =>
        {
            await Task.Run(() => Console.WriteLine($"This is middleware 3 Start"));
            await next.Invoke(context);
            await Task.Run(() => Console.WriteLine($"This is middleware 3 End"));
        };
    });

    #endregion Use中間件

    #region 最終把請求交給MVC

    app.UseRouting();
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "areas",
            pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");

        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });

    #endregion 最終把請求交給MVC
}

此處我們註釋掉了 中間件2 的 next 參數調用,使請求管道短路。下麵我們重新編譯後再次訪問 “/home/index” ,控制台輸出結果如下所示:

當委托不將請求傳遞給下一個委托時,它被稱為“讓請求管道短路”。 通常需要短路,因為這樣可以避免不必要的工作。 例如,靜態文件中間件可以處理對靜態文件的請求,並讓管道的其餘部分短路,從而起到終端中間件的作用。 

對於終端中間件,框架專門為我們提供了一個叫 app.Run(...) 的擴展方法,其實該方法的內部也是調用 app.Use(...) 這個方法的,下麵我們來看個示例:

/// <summary>
/// 配置Http請求處理管道
/// Http請求管道模型---就是Http請求被處理的步驟
/// 所謂管道,就是拿著HttpContext,經過多個步驟的加工,生成Response,這就是管道
/// </summary>
/// <param name="app"></param>
/// <param name="env"></param>
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    #region 環境參數

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    #endregion 環境參數

    //靜態文件中間件
    app.UseStaticFiles();

    #region Use中間件

    //中間件1
    app.Use(next =>
    {
        Console.WriteLine("middleware 1");
        return async context =>
        {
            await Task.Run(() =>
            {
                Console.WriteLine("");
                Console.WriteLine("===================================Middleware===================================");
                Console.WriteLine($"This is middleware 1 Start");
            });
            await next.Invoke(context);
            await Task.Run(() =>
            {
                Console.WriteLine($"This is middleware 1 End");
                Console.WriteLine("===================================Middleware===================================");
            });
        };
    });

    //中間件2
    app.Use(next =>
    {
        Console.WriteLine("middleware 2");
        return async context =>
        {
            await Task.Run(() => Console.WriteLine($"This is middleware 2 Start"));
            await next.Invoke(context); //可通過不調用 next 參數使請求管道短路
            await Task.Run(() => Console.WriteLine($"This is middleware 2 End"));
        };
    });

    //中間件3
    app.Use(next =>
    {
        Console.WriteLine("middleware 3");
        return async context =>
        {
            await Task.Run(() => Console.WriteLine($"This is middleware 3 Start"));
            await next.Invoke(context);
            await Task.Run(() => Console.WriteLine($"This is middleware 3 End"));
        };
    });

    #endregion Use中間件

    #region 終端中間件

    //app.Use(_ => handler);
    app.Run(async context =>
    {
        await Task.Run(() => Console.WriteLine($"This is Run"));
    });

    #endregion 終端中間件

    #region 最終把請求交給MVC

    app.UseRouting();
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "areas",
            pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");

        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });

    #endregion 最終把請求交給MVC
}

我們重新編譯後再次訪問 “/home/index” ,控制台輸出結果如下所示:

Run 委托不會收到 next 參數。第一個 Run 委托始終為終端,用於終止管道。Run 是一種約定。某些中間件組件可能會公開在管道末尾運行 Run[Middleware] 方法。

此外,app.Use(...) 方法還有另外一個重載,如下所示(中間件4):

/// <summary>
/// 配置Http請求處理管道
/// Http請求管道模型---就是Http請求被處理的步驟
/// 所謂管道,就是拿著HttpContext,經過多個步驟的加工,生成Response,這就是管道
/// </summary>
/// <param name="app"></param>
/// <param name="env"></param>
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    #region 環境參數

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    #endregion 環境參數

    //靜態文件中間件
    app.UseStaticFiles();

    #region Use中間件

    //中間件1
    app.Use(next =>
    {
        Console.WriteLine("middleware 1");
        return async context =>
        {
            await Task.Run(() =>
            {
                Console.WriteLine("");
                Console.WriteLine("===================================Middleware===================================");
                Console.WriteLine($"This is middleware 1 Start");
            });
            await next.Invoke(context);
            await Task.Run(() =>
            {
                Console.WriteLine($"This is middleware 1 End");
                Console.WriteLine("===================================Middleware===================================");
            });
        };
    });

    //中間件2
    app.Use(next =>
    {
        Console.WriteLine("middleware 2");
        return async context =>
        {
            await Task.Run(() => Console.WriteLine($"This is middleware 2 Start"));
            await next.Invoke(context); //可通過不調用 next 參數使請求管道短路
            await Task.Run(() => Console.WriteLine($"This is middleware 2 End"));
        };
    });

    //中間件3
    app.Use(next =>
    {
        Console.WriteLine("middleware 3");
        return async context =>
        {
            await Task.Run(() => Console.WriteLine($"This is middleware 3 Start"));
            await next.Invoke(context);
            await Task.Run(() => Console.WriteLine($"This is middleware 3 End"));
        };
    });

    //中間件4
    //Use方法的另外一個重載
    app.Use(async (context, next) =>
    {
        await Task.Run(() => Console.WriteLine($"This is middleware 4 Start"));
        await next();
        await Task.Run(() => Console.WriteLine($"This is middleware 4 End"));
    });

    #endregion Use中間件

    #region 終端中間件

    //app.Use(_ => handler);
    app.Run(async context =>
    {
        await Task.Run(() => Console.WriteLine($"This is Run"));
    });

    #endregion 終端中間件

    #region 最終把請求交給MVC

    app.UseRouting();
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "areas",
            pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");

        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });

    #endregion 最終把請求交給MVC
}

我們重新編譯後再次訪問 “/home/index” ,控制台輸出結果如下所示:

 

下麵我們結合ASP.NET Core源碼來分析下其實現原理: 

首先我們通過調試來看下 IApplicationBuilder 的實現類到底是啥?如下所示:

可以看出它的實現類是  Microsoft.AspNetCore.Builder.ApplicationBuilder ,我們找到 ApplicationBuilder 類的源碼,如下所示:

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Internal;

namespace Microsoft.AspNetCore.Builder
{
    public class ApplicationBuilder : IApplicationBuilder
    {
        private const string ServerFeaturesKey = "server.Features";
        private const string ApplicationServicesKey = "application.Services";

        private readonly IList<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>();

        public ApplicationBuilder(IServiceProvider serviceProvider)
        {
            Properties = new Dictionary<string, object>(StringComparer.Ordinal);
            ApplicationServices = serviceProvider;
        }

        public ApplicationBuilder(IServiceProvider serviceProvider, object server)
            : this(serviceProvider)
        {
            SetProperty(ServerFeaturesKey, server);
        }

        private ApplicationBuilder(ApplicationBuilder builder)
        {
            Properties = new CopyOnWriteDictionary<string, object>(builder.Properties, StringComparer.Ordinal);
        }

        public IServiceProvider ApplicationServices
        {
            get
            {
                return GetProperty<IServiceProvider>(ApplicationServicesKey);
            }
            set
            {
                SetProperty<IServiceProvider>(ApplicationServicesKey, value);
            }
        }

        public IFeatureCollection ServerFeatures
        {
            get
            {
                return GetProperty<IFeatureCollection>(ServerFeaturesKey);
            }
        }

        public IDictionary<string, object> Properties { get; }

        private T GetProperty<T>(string key)
        {
            object value;
            return Properties.TryGetValue(key, out value) ? (T)value : default(T);
        }

        private void SetProperty<T>(string key, T value)
        {
            Properties[key] = value;
        }

        public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
        {
            _components.Add(middleware);
            return this;
        }

        public IApplicationBuilder New()
        {
            return new ApplicationBuilder(this);
        }

        public RequestDelegate Build()
        {
            RequestDelegate app = context =>
            {
                // If we reach the end of the pipeline, but we have an endpoint, then something unexpected has happened.
                // This could happen if user code sets an endpoint, but they forgot to add the UseEndpoint middleware.
                var endpoint = context.GetEndpoint();
                var endpointRequestDelegate = endpoint?.RequestDelegate;
                if (endpointRequestDelegate != null)
                {
                    var message =
                        $"The request reached the end of the pipeline without executing the endpoint: '{endpoint.DisplayName}'. " +
                        $"Please register the EndpointMiddleware using '{nameof(IApplicationBuilder)}.UseEndpoints(...)' if using " +
                        $"routing.";
                    throw new InvalidOperationException(message);
                }

                context.Response.StatusCode = 404;
                return Task.CompletedTask;
            };

            foreach (var component in _components.Reverse())
            {
                app = component(app);
            }

            return app;
        }
    }
}

其中 RequestDelegate 委托的聲明,如下:

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Threading.Tasks;

namespace Microsoft.AspNetCore.Http
{
    /// <summary>
    /// A function that can process an HTTP request.
    /// </summary>
    /// <param name="context">The <see cref="HttpContext"/> for the request.</param>
    /// <returns>A task that represents the completion of request processing.</returns>
    public delegate Task RequestDelegate(HttpContext context);
}

仔細閱讀後可以發現其實 app.Use(...) 這個方法就只是將 Func<RequestDelegate, RequestDelegate> 類型的委托參數添加到 _components 這個集合中。

最終程式會調用 ApplicationBuilder 類的 Build() 方法去構建Http請求處理管道,接下來我們就重點來關註一下這個 Build() 方法,如下:

public RequestDelegate Build()
{
    RequestDelegate app = context =>
    {
        // If we reach the end of the pipeline, but we have an endpoint, then something unexpected has happened.
        // This could happen if user code sets an endpoint, but they forgot to add the UseEndpoint middleware.
        var endpoint = context.GetEndpoint();
        var endpointRequestDelegate = endpoint?.RequestDelegate;
        if (endpointRequestDelegate != null)
        {
            var message =
                $"The request reached the end of the pipeline without executing the endpoint: '{endpoint.DisplayName}'. " +
                $"Please register the EndpointMiddleware using '{nameof(IApplicationBuilder)}.UseEndpoints(...)' if using " +
                $"routing.";
            throw new InvalidOperationException(message);
        }

        context.Response.StatusCode = 404;
        return Task.CompletedTask;
    };

    foreach (var component in _components.Reverse())
    {
        app = component(app);
    }

    return app;
}

仔細觀察上面的源碼後我們可以發現: 

1、首先它是將 _components 這個集合反轉(即:_components.Reverse()),然後依次調用裡面的中間件(Func<RequestDelegate, RequestDelegate>委托),這也就解釋了為什麼網站啟動時我們的控制台會依次輸出 “middleware 3” 、“middleware 2”、“middleware 1” 的原因。 

2、調用反轉後的第一個中間件(即:註冊的最後一個中間件)時傳入的參數是狀態碼為404的 RequestDelegate 委托,作為預

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

-Advertisement-
Play Games
更多相關文章
  • 1. makefile簡單介紹: ▶ Windows系統里的 Visual studio/Dev C++等IDE工具(Integrated Development Environment)將C語言的預處理、編譯、彙編、鏈接等過程集成在一起,而在Linux系統下需要編寫GCC命令才能完成編譯操作。當項 ...
  • 背景 之前學Java屬於趕鴨子上架,草草學習基礎語法便直接做課程作業,許多概念問題仍不清楚,故在此梳理一下,主要參考廖雪峰和互聯網資料。 Java運行方式與JVM Java是介於編譯型語言(C++)和解釋型語言(Python)之間的。所有Java代碼先被編譯為.class文件,然後在Java虛擬機( ...
  • HashMap<Integer, String> map = new HashMap<>();map.put(11,"張三");map.put(12,"李四");map.put(13,"王五");map.put(12,"趙六");map.put(14,"陳皮");System.out.println ...
  • Java 8 之前,介面裡面只能寫抽象方法,不能寫實現方法 Java 8 開始是可以有方法實現的,可以在介面中添加預設方法和靜態方法 預設方法用 default 修飾,只能用在介面中,靜態方法用 static 修飾,並且介面中的預設方法、靜態方法可以同時有多個。 為什麼要用介面預設方法 舉一個很現實 ...
  • 一、環境安裝及配置 引用鏈接:Go語言環境安裝及配置 Go版本安裝 百度網盤msi地址:版本v1.18.1提取碼:m1mc GoLand工具 鏈接:【版本2020.1】提取碼:7x9o 2.1、安裝流程 goland-2020.1.exe安裝 2.2、破解 先下載壓縮包解壓後得到jetbrains- ...
  • 這篇文章我們來介紹一下 super,我相信大部分的人使用 super 都是使用這種方式; # 就是我有一個 class 比如說是 Male,然後繼承另外一個 class 比如是 Person,然後我在這個 Male 也就是它的子類的 init 函數裡面用 super().__init__() 來調用 ...
  • 各種鎖 可重入鎖、公平鎖\非公平鎖、獨占鎖\共用鎖、讀寫鎖 鎖狀態 重量級鎖、輕量級鎖、偏量鎖、鎖膨脹、鎖粗化、鎖自旋\自定義自旋 volatile輕量級鎖,鎖變數,可見性 synchronized使用方式,不同使用方式的底層實現,非公平鎖,鎖升級原理,鎖優化,降級 單例模式與synchronize... ...
  • 1. 概念 1.1 此次編寫字元串的查找和字元串的統計 1.2 編寫字元串常用的方法 1.2.1 string.isspace() 如果string中只包含空格,則返回true 1.2.2 string.isalnum() 如果string至少有一個字元並且所有字元都是字母或者數字則返回true 1 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...