ASP.NET Core 實戰:基於 Dapper 擴展你的數據訪問方法

来源:https://www.cnblogs.com/danvic712/archive/2019/02/25/10425501.html
-Advertisement-
Play Games

一、前言 在非靜態頁面的項目開發中,必定會涉及到對於資料庫的訪問,最開始呢,我們使用 Ado.Net,通過編寫 SQL 幫助類幫我們實現對於資料庫的快速訪問,後來,ORM(Object Relational Mapping,對象關係映射)出現了,我們開始使用 EF、Dapper、NHibernate ...


 一、前言

  在非靜態頁面的項目開發中,必定會涉及到對於資料庫的訪問,最開始呢,我們使用 Ado.Net,通過編寫 SQL 幫助類幫我們實現對於資料庫的快速訪問,後來,ORM(Object Relational Mapping,對象關係映射)出現了,我們開始使用 EF、Dapper、NHibernate,亦或是國人的 SqlSugar 代替我們原來的 SqlHelper.cs。通過這些 ORM 工具,我們可以很快速的將資料庫中的表與代碼中的類進行映射,同時,通過編寫 SQL 或是 Lambda 表達式的方式,更加便捷的實現對於數據層的訪問。

  就像文章標題中所說的這樣,在這個項目中我是使用的 Dapper 來進行的數據訪問,每個人都有自己的編程習慣,本篇文章只是介紹我在 Grapefruit.VuCore 這個項目中是如何基於 Dapper 創建自己使用的幫助方法的,不會涉及各種 ORM 工具的對比,請友善查看、討論。

  系列目錄地址:ASP.NET Core 項目實戰
  倉儲地址:https://github.com/Lanesra712/Grapefruit.VuCore

 二、Step by Step

  1、整體思路

  在 Grapefruit.VuCore 這個項目中,我選擇將 SQL 語句存儲在 XML 文件中(XML 以嵌入的資源的方式嵌入到程式集中),通過編寫中間件的方式,在程式運行時將存儲有 SQL 語句的 XML 程式集寫入到 Redis 緩存中。當使用到 SQL 語句時,通過 Redis 中的 Key 值進行獲取到 Value,從而將 SQL 語句與我們的代碼進行拆分。

  涉及到的類文件主要是在以下的類庫中,基於 Dapper 的數據訪問代碼則位於基礎構造層(02_Infrastructure)中,而使用到這些數據訪問代碼的,有且僅在位於領域層(03_Domain)中的代碼。同時,領域層的文件分佈結構和應用層(04_Applicatin)保持相同。

  2、擴展數據訪問方法

  在使用 Dapper 之前,我們首先需要在 Grapefruit.Infrastructure 這個類庫中添加對於 Dapper 的引用。同時,因為需要將 SQL 語句存儲到 Redis 緩存中,與之前使用 Redis 存儲 Token 時相同,這裡,也是使用的微軟的分散式緩存介面,因此,同樣需要添加對於此 DLL 的引用。

Install-Package Dapper
Install-Package Microsoft.Extensions.Caching.Abstractions

  在 Grapefruit.Infrastructure 類庫中創建一個 Dapper 文件夾,我們基於 Dapper 的擴展代碼全部置於此處,整個的代碼結構如下圖所示。

  在整個 Dapper 文件夾下類/介面/枚舉文件,主要可以按照功能分為三部分。

  2.1、輔助功能文件

  主要包含 DataBaseTypeEnum 這個枚舉類以及 SqlCommand 這個用來將存儲在 XML 中的 SQL 進行映射的幫助類。

  DataBaseTypeEnum 這個資料庫類型枚舉類主要定義了可以使用的資料庫類型。我們知道,Dapper 這個 ORM 主要是通過擴展 IDbConnection 介面,從而給我們提供附加的數據操作功能,而我們在創建資料庫連接對象時,不管是 SqlConnection 還是 MySqlConnection 最終對於資料庫最基礎的操作,都是繼承於 IDbConnection 這個介面。因此,我們可以在後面創建資料庫連接對象時,通過不同的枚舉值,創建針對不同資料庫操作的資料庫連接對象。

public enum DataBaseTypeEnum
{
    SqlServer = 1,
    MySql = 2,
    PostgreSql = 3,
    Oracle = 4
}

  SqlCommand 這個類文件只是定義了一些屬性,因為我是將 SQL 語句寫到 XML 文件中,同時會將 XML 文件存儲到 Redis 緩存中,因此,SqlCommand 這個類主要用來將我們獲取到的 SQL 語句與類文件做一個映射關係。

public class SqlCommand
{
    /// <summary>
    /// SQL語句名稱
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// SQL語句或存儲過程內容
    /// </summary>
    public string Sql { get; set; }
}

  2.2、SQL 存儲讀取

  對於 SQL 語句的存儲、讀取,我定義了一個 IDataRepository 介面,DataRepository 繼承於 IDataRepository 實現對於 SQL 語句的操作。

public interface IDataRepository
{
    /// <summary>
    /// 獲取 SQL 語句
    /// </summary>
    /// <param name="commandName"></param>
    /// <returns></returns>
    string GetCommandSQL(string commandName);

    /// <summary>
    /// 批量寫入 SQL 語句
    /// </summary>
    void LoadDataXmlStore();
}

  存儲 SQL 的 XML 我是以附加的資源存儲到 dll 中,因此,這裡我是通過載入 dll 的方式獲取到所有的 SQL 語句,之後,根據 Name 屬性判斷 Redis 中是否存在,當不存在時就寫入 Redis 緩存中。核心的代碼如下所示,如果你需要查看完整的代碼,可以去 Github 上查看。

/// <summary>
/// 載入dll中包含的SQL語句
/// </summary>
/// <param name="fullPath">命令名稱</param>
private void LoadCommandXml(string fullPath)
{
    SqlCommand command = null;
    Assembly dll = Assembly.LoadFile(fullPath);
    string[] xmlFiles = dll.GetManifestResourceNames();
    for (int i = 0; i < xmlFiles.Length; i++)
    {
        Stream stream = dll.GetManifestResourceStream(xmlFiles[i]);
        XElement rootNode = XElement.Load(stream);
        var targetNodes = from n in rootNode.Descendants("Command")
                          select n;
        foreach (var item in targetNodes)
        {
            command = new SqlCommand
            {
                Name = item.Attribute("Name").Value.ToString(),
                Sql = item.Value.ToString().Replace("<![CDATA[", "").Replace("]]>", "")
            };
            command.Sql = command.Sql.Replace("\r\n", "").Replace("\n", "").Trim();
            LoadSQL(command.Name, command.Sql);
        }
    }
}

/// <summary>
/// 載入SQL語句
/// </summary>
/// <param name="commandName">SQL語句名稱</param>
/// <param name="commandSQL">SQL語句內容</param>
private void LoadSQL(string commandName, string commandSQL)
{
    if (string.IsNullOrEmpty(commandName))
    {
        throw new ArgumentNullException("CommandName is null or empty!");
    }

    string result = GetCommandSQL(commandName);

    if (string.IsNullOrEmpty(result))
    {
        StoreToCache(commandName, commandSQL);
    }
}

  2.3、數據操作

  對於數據的操作,這裡我定義了 IDataAccess 這個介面,提供了同步、非同步的方式,實現對於數據的訪問。在項目開發中,對於數據的操作,更多的還是根據欄位值獲取對象、獲取對象集合、執行 SQL 獲取受影響的行數,獲取欄位值,所以,這裡主要就定義了這幾類的方法。

public interface IDataAccess
{
    /// 關閉資料庫連接
    bool CloseConnection(IDbConnection connection);

    /// 資料庫連接
    IDbConnection DbConnection();

    /// 執行SQL語句或存儲過程返回對象
    T Execute<T>(string sql, object param, bool hasTransaction = false, CommandType commandType = CommandType.Text);

    /// 執行SQL語句返回對象
    T Execute<T>(string sql, object param, IDbTransaction transaction, IDbConnection connection, CommandType commandType = CommandType.Text);

    /// 執行SQL語句或存儲過程返回對象
    Task<T> ExecuteAsync<T>(string sql, object param, bool hasTransaction = false, CommandType commandType = CommandType.Text);

    /// 執行SQL語句返回對象
    Task<T> ExecuteAsync<T>(string sql, object param, IDbTransaction transaction, IDbConnection connection, CommandType commandType = CommandType.Text);

    /// 執行SQL語句或存儲過程,返回IList<T>對象
    IList<T> ExecuteIList<T>(string sql, object param, bool hasTransaction = false, CommandType commandType = CommandType.Text);

    /// 執行SQL語句或存儲過程,返回IList<T>對象
    IList<T> ExecuteIList<T>(string sql, object param, IDbTransaction transaction, IDbConnection connection, CommandType commandType = CommandType.Text);

    /// 執行SQL語句或存儲過程,返回IList<T>對象
    Task<IList<T>> ExecuteIListAsync<T>(string sql, object param, bool hasTransaction = false, CommandType commandType = CommandType.Text);

    /// 執行SQL語句或存儲過程,返回IList<T>對象
    Task<IList<T>> ExecuteIListAsync<T>(string sql, object param, IDbTransaction transaction, IDbConnection connection, CommandType commandType = CommandType.Text);

    /// 執行SQL語句或存儲過程返回受影響行數
    int ExecuteNonQuery(string sql, object param, bool hasTransaction = false, CommandType commandType = CommandType.Text);

    /// 執行SQL語句或存儲過程返回受影響行數
    int ExecuteNonQuery(string sql, object param, IDbTransaction transaction, IDbConnection connection, CommandType commandType = CommandType.Text);

    /// 執行SQL語句或存儲過程返回受影響行數
    Task<int> ExecuteNonQueryAsync(string sql, object param, bool hasTransaction = false, CommandType commandType = CommandType.Text);

    /// 執行SQL語句或存儲過程返回受影響行數
    Task<int> ExecuteNonQueryAsync(string sql, object param, IDbTransaction transaction, IDbConnection connection, CommandType commandType = CommandType.Text);

    /// 執行語句返回T對象
    T ExecuteScalar<T>(string sql, object param, bool hasTransaction = false, CommandType commandType = CommandType.Text);

    /// 執行語句返回T對象
    Task<T> ExecuteScalarAsync<T>(string sql, object param, bool hasTransaction = false, CommandType commandType = CommandType.Text);
}

  在 IDataAccess 介面的功能實現與調用上,我採用了代理模式的方式,會涉及到 DataAccess、DataAccessProxy、DataAccessProxyFactory、DBManager 這四個類文件,之間的調用過程如下。

  DataAccess 是介面的實現類,通過下麵的幾個類進行隱藏,不直接暴露給外界方法。一些介面的實現如下所示。

/// <summary>
/// 創建資料庫連接
/// </summary>
/// <returns></returns>
public IDbConnection DbConnection()
{
    IDbConnection connection = null;
    switch (_dataBaseType)
    {
        case DataBaseTypeEnum.SqlServer:
            connection = new SqlConnection(_connectionString);
            break;
        case DataBaseTypeEnum.MySql:
            connection = new MySqlConnection(_connectionString);
            break;
    };
    return connection;
}

/// <summary>
/// 執行SQL語句或存儲過程,返回IList<T>對象
/// </summary>
/// <typeparam name="T">類型</typeparam>
/// <param name="sql">SQL語句 or 存儲過程名</param>
/// <param name="param">參數</param>
/// <param name="transaction">外部事務</param>
/// <param name="connection">資料庫連接</param>
/// <param name="commandType">命令類型</param>
/// <returns></returns>
public IList<T> ExecuteIList<T>(string sql, object param, IDbTransaction transaction, IDbConnection connection, CommandType commandType = CommandType.Text)
{
    IList<T> list = null;
    if (connection.State == ConnectionState.Closed)
    {
        connection.Open();
    }
    try
    {
        if (commandType == CommandType.Text)
        {
            list = connection.Query<T>(sql, param, transaction, true, null, CommandType.Text).ToList();
        }
        else
        {
            list = connection.Query<T>(sql, param, transaction, true, null, CommandType.StoredProcedure).ToList();
        }
    }
    catch (Exception ex)
    {
        _logger.LogError($"SQL語句:{sql},使用外部事務執行 ExecuteIList<T> 方法出錯,錯誤信息:{ex.Message}");
        throw ex;
    }
    return list;
}

  DBManager 是外界方法訪問的類,通過 CreateDataAccess 方法會創建一個 IDataAccess 對象,從而達到訪問介面中方法的目的。

[ThreadStatic]
private static IDataAccess _sMsSqlFactory;

/// <summary>
/// 
/// </summary>
/// <param name="cp"></param>
/// <returns></returns>
private static IDataAccess CreateDataAccess(ConnectionParameter cp)
{
    return new DataAccessProxy(DataAccessProxyFactory.Create(cp));
}

/// <summary>
/// MsSQL 資料庫連接字元串
/// </summary>
public static IDataAccess MsSQL
{
    get
    {
        ConnectionParameter cp;
        if (_sMsSqlFactory == null)
        {
            cp = new ConnectionParameter
            {
                ConnectionString = ConfigurationManager.GetConfig("ConnectionStrings:MsSQLConnection"),
                DataBaseType = DataBaseTypeEnum.SqlServer
            };
            _sMsSqlFactory = CreateDataAccess(cp);
        }
        return _sMsSqlFactory;
    }
}

  DataAccessProxy 就是實際介面功能實現類的代理,通過有參構造函數的方式進行調用,同時,類中繼承於 IDataAccess 的方法都是不實現的,都是通過 _dataAccess 調用介面中的方法。

/// <summary>
/// 
/// </summary>
private readonly IDataAccess _dataAccess;

/// <summary>
/// ctor
/// </summary>
/// <param name="dataAccess"></param>
public DataAccessProxy(IDataAccess dataAccess)
{
    _dataAccess = dataAccess ?? throw new ArgumentNullException("dataAccess is null");
}

/// <summary>
/// 執行SQL語句或存儲過程,返回IList<T>對象
/// </summary>
/// <typeparam name="T">類型</typeparam>
/// <param name="sql">SQL語句 or 存儲過程名</param>
/// <param name="param">參數</param>
/// <param name="transaction">外部事務</param>
/// <param name="connection">資料庫連接</param>
/// <param name="commandType">命令類型</param>
/// <returns></returns>
public IList<T> ExecuteIList<T>(string sql, object param, IDbTransaction transaction, IDbConnection connection, CommandType commandType = CommandType.Text)
{
    return _dataAccess.ExecuteIList<T>(sql, param, transaction, connection, commandType);
}

  DataAccessProxyFactory 這個類有一個 Create 靜態方法,通過實例化 DataAccess 類的方式返回 IDataAccess 介面,從而達到真正調用到介面實現類。

/// <summary>
/// 創建資料庫連接字元串
/// </summary>
/// <param name="cp"></param>
/// <returns></returns>
public static IDataAccess Create(ConnectionParameter cp)
{
    if (string.IsNullOrEmpty(cp.ConnectionString))
    {
        throw new ArgumentNullException("ConnectionString is null or empty!");
    }
    return new DataAccess(cp.ConnectionString, cp.DataBaseType);
}

  3、使用方法

  因為我們對於 SQL 語句的獲取全部是從緩存中獲取的,因此,我們需要在程式執行前將所有的 SQL 語句寫入 Redis 中。在 ASP.NET MVC 中,我們可以在 Application_Start 方法中進行調用,但是在 ASP.NET Core 中,我一直沒找到如何實現僅在程式開始運行時執行代碼,所以,這裡,我採用了中間件的形式將 SQL 語句存儲到 Redis 中,當然,你的每一次請求,都會調用到這個中間件。如果大家有好的方法,歡迎在評論區里指出。

public class DapperMiddleware
{
    private readonly ILogger _logger;

    private readonly IDataRepository _repository;

    private readonly RequestDelegate _request;

    /// <summary>
    /// ctor
    /// </summary>
    /// <param name="repository"></param>
    /// <param name="logger"></param>
    /// <param name="request"></param>
    public DapperMiddleware(IDataRepository repository, ILogger<DapperMiddleware> logger, RequestDelegate request)
    {
        _repository = repository;
        _logger = logger;
        _request = request;
    }

    /// <summary>
    /// 註入中間件到HttpContext中
    /// </summary>
    /// <param name="context"></param>
    /// <returns></returns>
    public async Task InvokeAsync(HttpContext context)
    {
        Stopwatch sw = new Stopwatch();
        sw.Start();

        //載入存儲xml的dll
        _repository.LoadDataXmlStore();

        sw.Stop();
        TimeSpan ts = sw.Elapsed;

        _logger.LogInformation($"載入存儲 XML 文件DLL,總共用時:{ts.TotalMinutes} 秒");

        await _request(context);
    }
}

  中間件的實現,只是調用了之前定義的 IDataRepository 介面中的 LoadDataXmlStore 方法,同時記錄下了載入的時間。在 DapperMiddlewareExtensions 這個靜態類中,定義了中間件的使用方法,之後我們在 Startup 的 Configure 方法里調用即可。

public static class DapperMiddlewareExtensions
{
    /// <summary>
    /// 調用中間件
    /// </summary>
    /// <param name="builder"></param>
    /// <returns></returns>
    public static IApplicationBuilder UseDapper(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<DapperMiddleware>();
    }
}

  中間件的調用代碼如下,同時,因為我們在中間件中通過依賴註入的方式使用到了 IDataRepository 介面,所以,我們也需要在 ConfigureServices 中註入該介面,這裡,採用單例的方式即可。

public class Startup
{
    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        //DI Sql Data
        services.AddTransient<IDataRepository, DataRepository>();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApiVersionDescriptionProvider provider)
    {
        //Load Sql Data
        app.UseDapper();
    }
}

   當所有的 SQL 語句寫入到緩存中後,我們就可以使用了,這裡的示例代碼實現的是上一篇(ASP.NET Core 實戰:基於 Jwt Token 的許可權控制全揭露)中,進行 Jwt Token 授權,驗證登錄用戶信息的功能。

  整個的調用過程如下圖所示。

  在 SecretDomain 中,我定義了一個 GetUserForLoginAsync 方法,通過帳戶名和密碼獲取用戶的信息,調用了之前定義的數據訪問方法。 

public class SecretDomain : ISecretDomain
{
    #region Initialize

    /// <summary>
    /// 倉儲介面
    /// </summary>
    private readonly IDataRepository _repository;

    /// <summary>
    /// ctor
    /// </summary>
    /// <param name="repository"></param>
    public SecretDomain(IDataRepository repository)
    {
        _repository = repository;
    }

    #endregion

    #region API Implements

    /// <summary>
    /// 根據帳戶名、密碼獲取用戶實體信息
    /// </summary>
    /// <param name="account">賬戶名</param>
    /// <param name="password">密碼</param>
    /// <returns></returns>
    public async Task<IdentityUser> GetUserForLoginAsync(string account, string password)
    {
        StringBuilder strSql = new StringBuilder();
        strSql.Append(_repository.GetCommandSQL("Secret_GetUserByLoginAsync"));
        string sql = strSql.ToString();

        return await DBManager.MsSQL.ExecuteAsync<IdentityUser>(sql, new
        {
            account,
            password
        });
    }

    #endregion
}

  XML 的結構如下所示,註意,這裡需要修改 XML 的屬性,生成操作改為附加的資源。

<?xml version="1.0" encoding="utf-8" ?>
<Commands>
  <Command Name="Secret_GetUserByLoginAsync">
    <![CDATA[
        SELECT Id ,Name ,Account ,Password ,Salt
          FROM IdentityUser
          WHERE Account=@account AND Password=@password;
      ]]>
  </Command>
  <Command Name="Secret_NewId">
    <![CDATA[
        select NEWID();
      ]]>
  </Command>
</Commands>

  因為篇幅原因,這裡就不把所有的代碼都列出來,整個調用的過程演示如下,如果有不明白的,或是有什麼好的建議的,歡迎在評論區中提出。因為,資料庫表並沒有設計好,這裡只是建了一個實驗用的表,,這裡我使用的是 SQL Server 2012,創建表的 SQL 語句如下。

USE [GrapefruitVuCore]
GO

ALTER TABLE [dbo].[IdentityUser] DROP CONSTRAINT [DF_User_Id]
GO

/****** Object:  Table [dbo].[IdentityUser]    Script Date: 2019/2/24 9:41:15 ******/
DROP TABLE [dbo].[IdentityUser]
GO

/****** Object:  Table [dbo].[IdentityUser]    Script Date: 2019/2/24 9:41:15 ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[IdentityUser](
    [Id] [uniqueidentifier] NOT NULL,
    [Name] [nvarchar](50) NOT NULL,
    [Account] [nvarchar](50) NOT NULL,
    [Password] [nvarchar](100) NOT NULL,
    [Salt] [uniqueidentifier] NOT NULL,
 CONSTRAINT [PK__User__3214EC07D257C709] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

ALTER TABLE [dbo].[IdentityUser] ADD  CONSTRAINT [DF_User_Id]  DEFAULT (newid()) FOR [Id]
GO

 三、總結

    這一章主要是介紹下我是如何使用 Dapper 構建我的數據訪問幫助方法的,每個人都會有自己的編程習慣,這裡只是給大家提供一個思路,適不適合你就不一定啦。因為年後工作開始變得多起來了,現在主要都是周末才能寫博客了,所以更新的速度會變慢些,同時,這一系列的文章,按我的設想,其實還有一兩篇文章差不多就結束了(VUE 前後端交互、Docker 部署),嗯,因為 Vue 那塊我還在學習中(其實就是很長時間沒看了。。。),所以接下來的一段時間可能會側重於 Vue 系列(Vue.js 牛刀小試),ASP.NET Core 系列可能會不定期更新,希望大家同樣可以多多關註啊。最後,感謝之前贊賞的小伙伴。


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

-Advertisement-
Play Games
更多相關文章
  • 文件上傳參考文檔:http://blog.didispace.com/spring-cloud-starter-dalston-2-4/ 文件下載參考文檔:https://blog.csdn.net/aaronsimon/article/details/82710979 我的spring boot ...
  • 導讀:Python貓是一隻喵星來客,它愛地球的一切,特別愛優雅而無所不能的 Python。我是它的人類朋友豌豆花下貓,被授權潤色與發表它的文章。如果你是第一次看到這個系列文章,那我強烈建議,請先看看它寫的前幾篇文章(鏈接見文末),相信你一定會愛上這隻神秘的哲學+極客貓的。不多說啦,一起來享用今天的“ ...
  • Win10構建Python全棧開發環境With WSL === [toc] 在學習Python全棧過程中,隨著後面地深入,進入實際項目地開發階段後,越發地發現,項目要使用的第三方軟體(redis git等)或者外界(支付寶開放平臺/微信開放平臺)聯繫越來越多,自己構建一個開發web服務,以及Pyth ...
  • 使用maven package打包項目時出現配置文件丟失的現象,此類問題解決辦法如下: 在web項目pom.xml 文件中添加如下: 在<build>標簽中添加如下配置: 說明:<include>標簽中配置自己項目中配置文件的尾碼名稱就闊以; 完畢! ...
  • 一、vector類型簡介 標準庫:集合或動態數組,我們可以放若幹對象放在裡面。 vector他能把其他對象裝進來,也被稱為容器 二、定義和初始化vector對象 (1)空的vector (2)元素拷貝的初始化方式 (3)C++11標準中,用列表初始化方法給值,用{}括起來 (4)創建指定數量的元素 ...
  • 原文地址:http://www.runoob.com/python/python-func-open.html ...
  • 一、前言 int,float,char,C++標準庫提供的類型:string,vector。 string:可變長字元串的處理;vector一種集合或者容器的概念。 二、string類型簡介 C++標準庫中的類型,代表一個可變長的字元串 char str[100] = “I Love China”; ...
  • 對二維數組指定的鍵名排序,首先大家想到的是array_multisort函數,關於array_multisort的用法我之前也寫了一篇廢話不多言,我們看個實例: 細心的朋友會看到,鍵名重置了,鍵名從0開始,顯然這可能不是我們想要的結果,那如何保持鍵名不變? 我們再看個示例: 這裡我們也可以精簡下ar ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...