首頁

目前文章總數:157 篇

  

最後更新:2024年 12月 07日

0039. .Net Core Blazor利用Middleware紀錄使用者IP

日期:2023年 06月 10日

標籤: C# Asp.NET Core Web Blazor Web

摘要:C# 學習筆記


應用所需:1. Visual Studio 2022
     2. .net core Web專案 (這邊用Blazor server示範)
範例檔案:連結
解決問題:開發的網站,如何記錄當前使用者(瀏覽者)IP
應用層面:紀錄不重複的用戶IP,可以知道大概有多少流量,更進一步可以依照IP位置做區域分析
基本介紹:本篇分為2大部分。
第一部分:範例專案介紹
第二部分:Demo展示結果






第一部分:範例專案介紹

Step 1:範例專案架構

1.顯示頁面,這邊假設整個網站有2個頁面 (使用者點擊任一頁面都會紀錄IP)
2. 網站建構:網站程序初始化的入口點,並且註冊注入、配置Middleware
3. 中介層:處理在頁面請求與響應回覆之間處理的工作,做邊紀錄用戶IP
4. 資料流:處理資料紀錄,與DB的工作
5. Sqlite靜態工具:為一個靜態類,基本的Sqlite + Dapper 的整併工具



Step 2:顯示頁面

頁面基本上只模擬用戶進入網站,可能頁面A,也可能頁面B但都會經過中介層紀錄IP
頁面A、B代碼:


@page "/"

<PageTitle>頁面A</PageTitle>

<h1>頁面A</h1>
<p role="status">範例頁面A</p>

<div>
    點擊此頁面會記錄當前操作者IP (頁面B)
</div>

@code {
}






Step 3:網站建構

創建初始化網站程序所需的注入
在CreateBuilder中添加以下:

builder.Services.AddSingleton<ISqliteRepository, SqliteRepository>();
builder.Services.AddHttpContextAccessor();


使用Build中,添加以下,實現中介層:

app.UseMiddleware<UserIPLoggingMiddleware>();




Step 4:中介層

在生命週期中,中介層會在靠近Resonse的地方處理,
確認正確性、路由、驗證後,確認該用戶資料都OK,才進行紀錄IP的動作


4-1. 實現Invoke,在每次用戶點擊網頁都會進入此層

public async Task Invoke(HttpContext context)
{
    var now = DateTime.Now;
    //1. 取得IP-關鍵代碼
    string userIpAddress = GetUserIP();
    
    //2. 存在才紀錄
    if (!string.IsNullOrEmpty(userIpAddress))
    {
        var row = new Model.UserIpRecordModel()
        {
            UserIp = userIpAddress,
            Date = now.ToString("yyyyMMdd")
        };
        _respoitory.CreatedRow(row);
    }
    await _next(context);
}


4-2. 取得IP-此為關鍵代碼,必須依照順序取得Header資料
※基本認知:所有瀏覽器傳到伺服器的Browser資料永遠都可以偽造(機器人),但正常操作的用戶還是可以取得正確IP
X-Forwarded-For :反向代理伺服器的多個IP
X-Real-IP : 用戶真實IP - 代理伺服器預設位置
HttpContext.HttpContext.Connection.RemoteIpAddress 取得Http上下文IP位址。

private string GetUserIP()
{
    //1. 取得"X-Forwarded-For" => 反向代理伺服器的多個IP,以;區隔,最左邊是用戶的IP
    string userIPAddress = _httpContextAccessor.HttpContext.Request.Headers["X-Forwarded-For"].FirstOrDefault();
    //2-1. 假設沒有經過反向代理
    if (string.IsNullOrEmpty(userIPAddress))
    {
        //2-2. 取得"X-Real-IP" => 常見的一些代理伺服器會將最終的用戶真實IP放在此標頭
        userIPAddress = _httpContextAccessor.HttpContext.Request.Headers["X-Real-IP"].FirstOrDefault();
        if (string.IsNullOrEmpty(userIPAddress))
        {
            //3. 預設瀏覽器請求的IP
            userIPAddress = _httpContextAccessor.HttpContext.Connection.RemoteIpAddress?.ToString();
        }
    }
    return userIPAddress;
}



Step 5:資料流

中介層確定可寫入資料後,會進入Repository將Insert SQL寫入
實際寫入才會呼叫Util,這邊是用Sqlite做個範例
※即使是小型專案,也盡量避免用INSERT OR REPLACE INTO,這個在叫用率高的情境下,語法效能不佳,對DB是負擔
這是Sqlite專用語法。

        /// <summary>
        /// 更新IP資料
        /// </summary>
        public void CreatedRow(UserIpRecordModel row)
        {
            var sql = $@"
INSERT OR REPLACE INTO UserIpRecord (UserIp, Date, CreatedTime)
                             VALUES (@UserIp, @Date,  datetime('now', 'localtime'))
;";
            SqlLiteDbUtil.Master.Execute(sql, row);
        }




Step 6:Sqlite靜態工具

這邊實現一個Dapper的Sqlite資料庫操作。
資料表相對簡單,主鍵為以下:
UserIp:用戶IP
Date:屬於年月日 (yyyyMMdd)

    public static class SqlLiteDbUtil
    {
        //1. 宣告初始化建構資料庫的位址 ※同專案位置
        public const string DatabaseFileName = @"MyUserLoginIp.db";
        public const string ConnectionString = "Data Source=" + DatabaseFileName;

        //2-1. 靜態只會觸發第一個初始化,所以這邊進行建構
        static SqlLiteDbUtil()
        {
            Master = new SQLiteConnection(ConnectionString);
            CreateDatabaseIfNotExists();
        }

        //2-2. 利用Dapper的IDbConnection 實現資料庫的操作
        public static IDbConnection Master { get; private set; }

        #region 資料庫建構
        private static void CreateDatabaseIfNotExists()
        {
            //3. 如果資料庫不存在就建立初始的表
            if (!File.Exists(DatabaseFileName))
            {
                //Create Local Database
                Master.Open();

                //Initial Tables
                CreateDatabase();
            }

            void CreateDatabase()
            {
                Master.Execute($@"
CREATE TABLE UserIpRecord (
    UserIp TEXT NOT NULL,
    Date TEXT NOT NULL,
    CreatedTime DATETIME,
    PRIMARY KEY (UserIp, Date)
);");
            }
            #endregion
        }
    }






第二部分:Demo展示結果

Step 1:檔案位置

這是使用的範例檔案,Github連結
然後將檔案部署起來,然後透過兩個位置做連結


Step 2:第一個位址連線

下載範例檔案後執行建置運行
IP:71.23


Step 3:第二個位址連線案

下載範例檔案後執行建置運行
IP:51.70


Step 4:連到Server

我們連到Server看到Sqlite產生的檔案,用Sqlite Browser工具開啟檢視


Step 5:驗證結果

可以看到IP都有紀錄,且在相同IP位址時,使用者即使切換頁面操作IP也不會重複 ※實務上應用緩存、Redis等紀錄,若重複時不對資料庫做任何操作