.Net Core MVC 網站開發(Ninesky) 2.3、項目架構調整(續)-使用配置文件動態註入

来源:http://www.cnblogs.com/mzwhj/archive/2016/12/26/6224237.html
-Advertisement-
Play Games

上次實現了依賴註入,但是web項目必須要引用業務邏輯層和數據存儲層的實現,項目解耦並不完全;另一方面,要同時註入業務邏輯層和數據訪問層,註入的服務直接寫在Startup中顯得非常臃腫。理想的方式是,web項目近引用介面而不引用實現,在配置文件中進行配置實現程式集合類,註入業務邏輯層而不必註入數據訪問 ...


上次實現了依賴註入,但是web項目必須要引用業務邏輯層和數據存儲層的實現,項目解耦並不完全;另一方面,要同時註入業務邏輯層和數據訪問層,註入的服務直接寫在Startup中顯得非常臃腫。理想的方式是,web項目近引用介面而不引用實現,在配置文件中進行配置實現程式集合類,註入業務邏輯層而不必註入數據訪問層。

一、數據訪問層

在項目中摒棄數據訪問層或者使用EntityFramework作為數據訪問層。

在項目中數據訪問層主要實現數據的存儲,仔細看一下EntityFramework發現DbContext的功能完全實現了查、增、刪、改等各種操作,並且有緩存等功能,本身就實現了倉儲模式,並且比自己封裝的數據存儲層的功能還強大,乾脆在項目中用EntityFramework作為數據存儲層。刪除掉Ninesky.InterfaceDataLibrary項目和Ninesky.DataLibrary項目。

註:項目結構調整的確實太頻繁了,以後一段時間內絕不再調整了。

二、實現業務邏輯層。

添加業務邏輯層介面項目Ninesky.InterfaceBase

1、添加介面基類介面InterfaceBaseService,添加基本的查、增、刪、改方法

using Ninesky.Models;
using System;
using System.Linq;
using System.Linq.Expressions;

namespace Ninesky.InterfaceBase
{
    /// <summary>
    ///  服務基礎介面
    /// </summary>
    public interface InterfaceBaseService<T> where T:class
    {

        /// <summary>
        /// 添加
        /// </summary>
        /// <param name="entity">實體</param>
        /// <param name="isSave">是否立即保存</param>
        /// <returns>添加的記錄數</returns>
        int Add(T entity, bool isSave = true);

        /// <summary>
        /// 添加[批量]
        /// </summary>
        /// <param name="entities">實體</param>
        /// <param name="isSave">是否立即保存</param>
        /// <returns>添加的記錄數</returns>
        int AddRange(T[] entities, bool isSave = true);

        /// <summary>
        /// 查詢記錄數
        /// </summary>
        /// <param name="predicate">查詢條件</param>
        /// <returns>記錄數</returns>
        int Count(Expression<Func<T, bool>> predicate);

        /// <summary>
        /// 查詢是否存在
        /// </summary>
        /// <param name="predicate">查詢條件</param>
        /// <returns>是否存在</returns>
        bool Exists(Expression<Func<T, bool>> predicate);

        /// <summary>
        /// 查找
        /// </summary>
        /// <param name="Id">主鍵</param>
        /// <returns></returns>
        T Find(int Id);

        /// <summary>
        /// 查找
        /// </summary>
        /// <param name="keyValues">主鍵</param>
        /// <returns></returns>
        T Find(object[] keyValues);


        /// <summary>
        /// 查找
        /// </summary>
        /// <param name="predicate">查詢條件</param>
        /// <returns></returns>
        T Find(Expression<Func<T, bool>> predicate);


        IQueryable<T> FindList<TKey>(int number, Expression<Func<T, bool>> predicate);

        /// <summary>
        /// 查詢
        /// </summary>
        /// <typeparam name="TKey">排序屬性</typeparam>
        /// <param name="number">顯示數量[小於等於0-不啟用]</param>
        /// <param name="predicate">查詢條件</param>
        /// <param name="keySelector">排序</param>
        /// <param name="isAsc">正序</param>
        /// <returns></returns>
        IQueryable<T> FindList<TKey>(int number, Expression<Func<T, bool>> predicate, Expression<Func<T, TKey>> keySelector, bool isAsc);

        /// <summary>
        /// 查詢[分頁]
        /// </summary>
        /// <typeparam name="TKey">排序屬性</typeparam>
        /// <param name="predicate">查詢條件</param>
        /// <param name="keySelector">排序</param>
        /// <param name="isAsc">是否正序</param>
        /// <param name="paging">分頁數據</param>
        /// <returns></returns>
        Paging<T> FindList<TKey>(Expression<Func<T, bool>> predicate, Expression<Func<T, TKey>> keySelector, bool isAsc, Paging<T> paging);

        /// <summary>
        /// 查詢[分頁]
        /// </summary>
        /// <typeparam name="TKey">排序屬性</typeparam>
        /// <param name="predicate">查詢條件</param>
        /// <param name="keySelector">排序</param>
        /// <param name="isAsc">是否正序</param>
        /// <param name="pageIndex">當前頁</param>
        /// <param name="pageSize">每頁記錄數</param>
        /// <returns></returns>
        Paging<T> FindList<TKey>(Expression<Func<T, bool>> predicate, Expression<Func<T, TKey>> keySelector, bool isAsc, int pageIndex, int pageSize);

        /// <summary>
        /// 刪除
        /// </summary>
        /// <param name="entity">實體</param>
        /// <param name="isSave">是否立即保存</param>
        /// <returns>是否刪除成功</returns>
        bool Remove(T entity, bool isSave = true);

        /// <summary>
        /// 刪除[批量]
        /// </summary>
        /// <param name="entities">實體數組</param>
        /// <param name="isSave">是否立即保存</param>
        /// <returns>成功刪除的記錄數</returns>
        int RemoveRange(T[] entities, bool isSave = true);


        /// <summary>
        ///  保存到資料庫
        /// </summary>
        /// <returns>更改的記錄數</returns>
        int SaveChanges();

        /// <summary>
        /// 更新
        /// </summary>
        /// <param name="entity">實體</param>
        /// <param name="isSave">是否立即保存</param>
        /// <returns>是否保存成功</returns>
        bool Update(T entity, bool isSave = true);

        /// <summary>
        /// 更新[批量]
        /// </summary>
        /// <param name="entities">實體數組</param>
        /// <param name="isSave">是否立即保存</param>
        /// <returns>更新成功的記錄數</returns>
        int UpdateRange(T[] entities, bool isSave = true);

    }
}
View Code

2、在Ninesky.Base中添加,介面InterfaceBaseService的實現類BaseService.cs

using Microsoft.EntityFrameworkCore;
using Ninesky.InterfaceBase;
using Ninesky.Models;
using System;
using System.Linq;
using System.Linq.Expressions;

namespace Ninesky.Base
{
    /// <summary>
    /// 服務基類
    /// </summary>
    public class BaseService<T>:InterfaceBaseService<T> where T:class
    {
        protected DbContext _dbContext;
        public BaseService(DbContext dbContext)
        {
            _dbContext = dbContext;
        }

        public virtual int Add(T entity, bool isSave = true)
        {
            _dbContext.Set<T>().Add(entity);
            if (isSave) return _dbContext.SaveChanges();
            else return 0;
        }

        public virtual int AddRange(T[] entities, bool isSave = true)
        {
            _dbContext.Set<T>().AddRange(entities);
            if (isSave) return _dbContext.SaveChanges();
            else return 0;
        }

        /// <summary>
        /// 查詢記錄數
        /// </summary>
        /// <param name="predicate">查詢條件</param>
        /// <returns>記錄數</returns>
        public virtual int Count(Expression<Func<T, bool>> predicate)
        {
            return _dbContext.Set<T>().Count(predicate);
        }

        /// <summary>
        /// 查詢是否存在
        /// </summary>
        /// <param name="predicate">查詢條件</param>
        /// <returns>是否存在</returns>
        public virtual bool Exists(Expression<Func<T, bool>> predicate)
        {
            return Count(predicate) > 0;
        }

        /// <summary>
        /// 查找
        /// </summary>
        /// <param name="Id">主鍵</param>
        /// <returns></returns>
        public virtual T Find(int Id)
        {
            return _dbContext.Set<T>().Find(Id);
        }

        public virtual T Find(object[] keyValues)
        {
            return _dbContext.Set<T>().Find(keyValues);
        }

        public virtual T Find(Expression<Func<T, bool>> predicate)
        {
            return _dbContext.Set<T>().SingleOrDefault(predicate);
        }

        public virtual IQueryable<T> FindList<TKey>(int number, Expression<Func<T, bool>> predicate)
        {
            var entityList = _dbContext.Set<T>().Where(predicate);
            if (number > 0) return entityList.Take(number);
            else return entityList;
        }

        public virtual IQueryable<T> FindList<TKey>(int number, Expression<Func<T, bool>> predicate, Expression<Func<T, TKey>> keySelector, bool isAsc)
        {
            var entityList = _dbContext.Set<T>().Where(predicate);
            if (isAsc) entityList = entityList.OrderBy(keySelector);
            else entityList.OrderByDescending(keySelector);
            if (number > 0) return entityList.Take(number);
            else return entityList;
        }


        public virtual Paging<T> FindList<TKey>(Expression<Func<T, bool>> predicate, Expression<Func<T, TKey>> keySelector, bool isAsc, Paging<T> paging)
        {
            var entityList = _dbContext.Set<T>().Where(predicate);
            paging.Total = entityList.Count();
            if (isAsc) entityList = entityList.OrderBy(keySelector);
            else entityList.OrderByDescending(keySelector);
            paging.Entities = entityList.Skip((paging.PageIndex - 1) * paging.PageSize).Take(paging.PageSize).ToList();
            return paging;
        }

        public virtual Paging<T> FindList<TKey>(Expression<Func<T, bool>> predicate, Expression<Func<T, TKey>> keySelector, bool isAsc, int pageIndex, int pageSize)
        {
            Paging<T> paging = new Paging<T> { PageIndex = pageIndex, PageSize = pageSize };
            return FindList(predicate, keySelector, isAsc, paging);
        }

        /// <summary>
        /// 刪除
        /// </summary>
        /// <param name="entity">實體</param>
        /// <param name="isSave">是否立即保存</param>
        /// <returns>是否刪除成功</returns>
        public virtual bool Remove(T entity, bool isSave = true)
        {
            _dbContext.Set<T>().Remove(entity);
            if (isSave) return _dbContext.SaveChanges() > 0;
            else return false;
        }

        /// <summary>
        /// 刪除[批量]
        /// </summary>
        /// <param name="entities">實體數組</param>
        /// <param name="isSave">是否立即保存</param>
        /// <returns>成功刪除的記錄數</returns>
        public virtual int RemoveRange(T[] entities, bool isSave = true)
        {
            _dbContext.Set<T>().RemoveRange(entities);
            if (isSave) return _dbContext.SaveChanges();
            else return 0;
        }


        public virtual int SaveChanges()
        {
            return _dbContext.SaveChanges();
        }

        /// <summary>
        /// 更新
        /// </summary>
        /// <param name="entity">實體</param>
        /// <param name="isSave">是否立即保存</param>
        /// <returns>是否保存成功</returns>
        public virtual bool Update(T entity, bool isSave = true)
        {
            _dbContext.Set<T>().Update(entity);
            if (isSave) return _dbContext.SaveChanges() > 0;
            else return false;
        }

        /// <summary>
        /// 更新[批量]
        /// </summary>
        /// <param name="entities">實體數組</param>
        /// <param name="isSave">是否立即保存</param>
        /// <returns>更新成功的記錄數</returns>
        public virtual int UpdateRange(T[] entities, bool isSave = true)
        {
            _dbContext.Set<T>().UpdateRange(entities);
            if (isSave) return _dbContext.SaveChanges();
            else return 0;
        }
    }
}
View Code

3、在Ninesky.InterfaceBase項目中添加欄目介面InterfaceCategoryService.cs,新增了一個Findtree的方法

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

namespace Ninesky.InterfaceBase
{
    /// <summary>
    /// 欄目服務介面
    /// </summary>
    public interface InterfaceCategoryService:InterfaceBaseService<Category>
    {
        /// <summary>
        /// 查找樹形菜單
        /// </summary>
        /// <param name="categoryType">欄目類型,可以為空</param>
        /// <returns></returns>
        List<Category> FindTree(CategoryType? categoryType);
    }
}
View Code

4、在Ninesky.Base中添加欄目介面的實現類CategoryService.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ninesky.Models;
using Ninesky.InterfaceBase;

namespace Ninesky.Base
{
    /// <summary>
    /// 欄目服務類
    /// </summary>
    public class CategoryService:BaseService<Category>,InterfaceCategoryService
    {
        public CategoryService(DbContext dbContext):base(dbContext)
        {
        }
        /// <summary>
        /// 查找
        /// </summary>
        /// <param name="Id">欄目ID</param>
        /// <returns></returns>
        public override Category Find(int Id)
        {
            return _dbContext.Set<Category>().Include("General").Include("Page").Include("Link").SingleOrDefault(c => c.CategoryId == Id);
        }

        /// <summary>
        /// 查找樹形菜單
        /// </summary>
        /// <param name="categoryType">欄目類型,可以為空</param>
        /// <returns></returns>
        public List<Category> FindTree(CategoryType? categoryType)
        {
            var categories = _dbContext.Set<Category>().AsQueryable();
            //根據欄目類型分類處理
            switch (categoryType)
            {
                case null:
                    break;
                case CategoryType.General:
                    categories = categories.Where(c => c.Type == categoryType);
                    break;
                    //預設-Page或Link類型
                default:
                    //Id數組-含本欄目及父欄目
                    List<int> idArray = new List<int>();
                    //查找欄目id及父欄目路徑
                    var categoryArray = categories.Where(c => c.Type == categoryType).Select(c => new { CategoryId = c.CategoryId, ParentPath = c.ParentPath });
                    if(categoryArray != null)
                    {
                        //添加欄目ID到
                        idArray.AddRange(categoryArray.Select(c => c.CategoryId));
                        foreach (var parentPath in categoryArray.Select(c=>c.ParentPath))
                        {
                            var parentIdArray = parentPath.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                            if (parentIdArray != null)
                            {
                                int parseId = 0;
                                foreach(var parentId in parentIdArray)
                                {
                                    if (int.TryParse(parentId, out parseId)) idArray.Add(parseId);
                                }
                            }
                        }
                    }
                    categories = categories.Where(c => idArray.Contains(c.CategoryId));
                    break;
            }
            return categories.OrderBy(c => c.ParentPath).ThenBy(C => C.Order).ToList();
        }
    }
}
View Code

三、實現dll動態載入和註入

要在web項目中對實現類進行解耦和註入,那麼項目只能對介面進行依賴,解除對實現的依賴,然後在配置文件中配置實現的程式集和註入的服務,在Startup類中讀取配置文件並載入程式集,然後實現介面的註入。

1、解除實現類依賴

在Web項目中添加對Ninesky.InterfaceBase項目的引用,解除對Ninesky.Base項目的引用。

2、實現註入的配置文件

首先在Models項目中實現註入服務類型配置項ServiceItem

using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

namespace Ninesky.Models
{
    /// <summary>
    /// 註入服務配置
    /// </summary>
    public class ServiceItem
    {
        /// <summary>
        /// 服務類型[含命名空間]
        /// </summary>
        public string ServiceType { get; set; }

        /// <summary>
        /// 實現類類型[含命名空間]
        /// </summary>
        public string ImplementationType { get; set; }

        /// <summary>
        /// 生命周期
        /// </summary>
        [JsonConverter(typeof(StringEnumConverter))]
        public ServiceLifetime LifeTime { get; set; }
    }
}
View Code

然後在Models項目中實現註入需要載入的程式集配置項 AssemblyItem

using System.Collections.Generic;

namespace Ninesky.Models
{
    /// <summary>
    /// 程式集註入項目
    /// </summary>
    public class AssemblyItem
    {
        /// <summary>
        ///  服務的程式集名稱[不含尾碼]
        /// </summary>
        public string ServiceAssembly { get; set; }
        /// <summary>
        /// 實現程式集名稱[含尾碼.dll]
        /// </summary>
        public string ImplementationAssembly { get; set; }

        /// <summary>
        /// 註入服務集合
        /// </summary>
        public List<ServiceItem> DICollections { get; set; }
    }
}
View Code

添加配置文件

在Web項目中添加配置文件service.json

{
  "AssemblyCollections": [
    {
      "ServiceAssembly": "Ninesky.InterfaceBase",
      "ImplementationAssembly": "Ninesky.Base.dll",
      "DICollections": [
        {
          "ServiceType": "Ninesky.InterfaceBase.InterfaceCategoryService",
          "ImplementationType": "Ninesky.Base.CategoryService",
          "LifeTime": "Scoped"
        }
      ]
    }
  ]
}
View Code

可以看到配置文件的鍵值對於AssemblyItem類和ServiceItem類對應。集合的服務程式集為Ninesky.InterfaceBase,實現程式集為Ninesky.Base.dll,註入的服務為Ninesky.InterfaceBase.InterfaceCategoryService,實現類是Ninesky.Base.CategoryService。

讀取配置文件並綁定到類型

在Startup只需要一行到即可綁定配置到類型。讀取配置文件並綁定的詳細操作見《Asp.Net Core自定義配置並綁定

var assemblyCollections =  new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("service.json").Build().GetSection("AssemblyCollections").Get<List<AssemblyItem>>();
View Code

3、進行註入

在assemblyCollections變數載入了配置文件後使用如下代碼即可實現註入

            foreach(var assembly in assemblyCollections)
            {
                var serviceAssembly = Assembly.Load(new AssemblyName(assembly.ServiceAssembly));
                var implementationAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(AppContext.BaseDirectory + "//" + assembly.ImplementationAssembly);
                foreach(var service in assembly.DICollections)
                {
                    services.Add(new ServiceDescriptor(serviceAssembly.GetType(service.ServiceType), implementationAssembly.GetType(service.ImplementationType), service.LifeTime));
                }
            }
View Code

代碼中可以看到載入介面程式集使用的方法是Assembly.Load(new AssemblyName(assembly.ServiceAssembly)),這是因為項目引用了介面程式集的項目,載入程式集的時候只需要提供程式集的名稱就可以。

載入實現類所在程式集的時候使用的是AssemblyLoadContext.Default.LoadFromAssemblyPath(AppContext.BaseDirectory + "//" + assembly.ImplementationAssembly)。在.Net Core中Assembly沒有了LoadFrom方法,僅有一個Load方法載入已引用的程式集。多方搜索資料才找到AssemblyLoadContext中有一個方法可以不需要引用項目可以動態載入Dll,但必須包含Dll的完整路徑。

 

 

到這裡就完整實現瞭解耦,現在項目結構看起來是這樣子

image

解耦後有些麻煩的是修改Base項目的代碼後運行項目會出錯,必鬚生成項目後將Base項目生成的Ninesky.Base.dll和Ninesky.Base.pdb複製到Web項目的bin\Debug\netcoreapp1.1目錄下才能正常運行。

F5運行一下可以看到正常讀出了數據。

image

 

四、其他

 

代碼托管地址:https://git.oschina.net/ninesky/Ninesky

文章發佈地址:http://www.ninesky.cn

                 http://mzwhj.cnblogs.com/

代碼包下載:Ninesky2.3、項目架構調整(續)-使用配置文件動態註入.rar

 

返回目錄


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

-Advertisement-
Play Games
更多相關文章
  • 分享一個MySQL分庫備份腳本(原) 開發思路: 1.路徑:規定備份到什麼位置,把路徑(先判斷是否存在,不存在創建一個目錄)先定義好,我的路徑:/mysql/backup,每個備份用壓縮提升效率,帶上時間方便整理 2.取資料庫:抓取資料庫名稱,我用的awk和grep配合取資料庫的名稱(如果想按照表備 ...
  • 自己簡單寫的一個php服務的啟動腳本和大家分享 思路(實現的原理): 1:function模塊+case語句多分支判斷 2:通過添加# chkconfig: 2345 43 89註釋實現開機自啟動(前提是把腳本放入/etc/init.d/目錄下 然後chmod給可執行許可權,然後chkconfig - ...
  • MySQL(或者其它服務)的keepalived高可用監控腳本 開發腳本需求:我們知道,keepalive是基於虛擬ip的存活來判斷是否搶占master的機制的,但是如果我們做了MySQL的keepalived的高可用的時候,就要考慮一種情況的發生,那就是如果機器網卡並沒有斷,二十由於MySQL服務 ...
  • 驗證碼類 使用方法 HTML 視圖JS ...
  • 1 自定義控制項與用戶控制項區別 WinForm中, 用戶控制項(User Control):繼承自 UserControl,主要用於開發 Container 控制項,Container控制項可以添加其他Controls控制項 自定義控制項(Custom Control):繼承自 Control,主要用於開發wi ...
  • 年關將近,整個人已經沒有了工作和寫作的激情,估計這個時候很多人跟我差不多,該相親的相親,該聚會喝酒的聚會喝酒,總之就是沒有了幹活的心思(我有很多想法,但就是叫不動我的手腳,所以我只能看著別人在做我想做的事,吃我想吃的東西。)。本人由上個月的每周四五篇,到現在的文章縮短到每周一篇,說個實話,現在的一篇 ...
  • .NET Core和 .NET 4.6中 的C# 6/7 中的編譯器Roslyn 一個重要的特性就是"Compiler as a Service",簡單的講,就是就是將編譯器開放為一種可在代碼中調用的服務, 通常在工作流引擎 或是規則引擎中都需要一項功能是計算表達式,這樣的任務很容易就用Roslyn... ...
  • 當某個請求能夠被成功路由的前提是它滿足某個Route對象設置的路由規則,具體來說,當前請求的URL不僅需要滿足路由模板體現的路徑模式,請求還需要滿足Route對象的所有約束。路由系統採用IRouteConstraint介面來表示路由約束,所以我們在接下來的內容中將路由約束統稱為RouteConstr... ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...