EFcore與動態模型

来源:http://www.cnblogs.com/dxp909/archive/2017/02/25/6442013.html
-Advertisement-
Play Games

在開發商城系統的時候,大家會遇到這樣的需求,商城系統里支持多種商品類型,比如衣服,手機,首飾等,每一種產品類型都有自己獨有的參數信息,比如衣服有顏色,首飾有材質等,大家可以上淘寶看一下就明白了。現在的問題是,如果我程式發佈後,要想增加一種新的商品類型怎麼辦,如果不在程式設計時考慮這個問題的話,可能每 ...


  在開發商城系統的時候,大家會遇到這樣的需求,商城系統里支持多種商品類型,比如衣服,手機,首飾等,每一種產品類型都有自己獨有的參數信息,比如衣服有顏色,首飾有材質等,大家可以上淘寶看一下就明白了。現在的問題是,如果我程式發佈後,要想增加一種新的商品類型怎麼辦,如果不在程式設計時考慮這個問題的話,可能每增加一個商品類型,就要增加對應商品類型的管理程式,並重新發佈上線,對於維護來說成本會很高。有沒有簡單的方式可以快速增加新類型的支持?下麵介紹的方案是這樣的,首先把模型以配置的方式保存到配置文件中,在程式啟動時解析模型信息編譯成具體的類,然後通過ef實現動態編譯類的資料庫操作,如果新增類型,首先改下配置文件,然後在資料庫中創建對應的資料庫表,重啟應用程式即可。

  要實現這樣的功能,需要解決以下幾個問題:

  1,如何實現動態模型的配置管理

  2,如何根據模型配置在運行時動態生成類型

  3,如何讓ef識別動態類型

  4,如何結合ef對動態類型信息進行操作,比如查詢,增加等

  一、如何實現動態模型的配置管理

  這個問題解決的方案是,把模型的信息作為系統的一個配置文件,在系統運行時可以獲取到模型配置信息。

  首先定義一個類表示一個動態模型,代碼如下:

 public class RuntimeModelMeta
    {
        public int ModelId { get; set; }
        public string ModelName { get; set; }//模型名稱
        public string ClassName { get; set; }//類名稱
        public string Properties{get;set;}//屬性集合json序列化結果
        
        public class ModelPropertyMeta
        {
            public string Name { get; set; }//對應的中文名稱
            public string PropertyName { get; set; } //類屬性名稱  
       public int Length { get; set; }//數據長度,主要用於string類型 

        public bool IsRequired { get; set; }//是否必須輸入,用於數據驗證
        public string ValueType { get; set; }//數據類型,可以是字元串,日期,bool等
        }
    }

  

  然後定義個配置類:

  public class RuntimeModelMetaConfig
    {
        public RuntimeModelMeta[] Metas { get; set; }
    }

  增加配置文件,文件名稱為runtimemodelconfig.json,結構如下:

{
  "RuntimeModelMetaConfig": {
    "Metas": [
      {
        "ModelId": 1,
        "ModelName": "衣服",
        "ClassName": "BareDiamond",
       
        "ModelProperties": [
          {
            "Name": "尺寸",
            "PropertyName": "Size",
          },
          {
            "Name": "顏色",
            "PropertyName": "Color",
          }
        ]
      }
    ]
  }
}

  下一步再asp.net core mvc的Startup類的構造方法中加入配置文件

    public Startup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                .AddJsonFile("runtimemodelconfig.json", optional:true,reloadOnChange:true)
                .AddEnvironmentVariables();

            if (env.IsDevelopment())
            {
                builder.AddApplicationInsightsSettings(developerMode: true);
            }
            Configuration = builder.Build();
        }

  然後再public void ConfigureServices(IServiceCollection services)方法中,獲取到配置信息,代碼如下:

     public void ConfigureServices(IServiceCollection services)
        {
            。。。。。。
            services.Configure<RuntimeModelMetaConfig>(Configuration.GetSection("RuntimeModelMetaConfig"));
           。。。。。。
        }

  到此就完成了配置信息的管理,在後續代碼中可以通過依賴註入方式獲取到IOptions<RuntimeModelMetaConfig>對象,然後通過IOptions<RuntimeModelMetaConfig>.Value.Metas獲取到所有模型的信息。為了方便模型信息的管理,我這裡定義了一個IRuntimeModelProvider介面,結構如下:

public interface IRuntimeModelProvider
    {
        Type GetType(int modelId);
     Type[] GetTypes();
    }

   IRuntimeModelProvider.GetType方法可以通過modelId獲取到對應的動態類型Type信息,GetTypes方法返回所有的動態類型信息。這個介面實現請看下麵介紹。

  二、如何根據模型配置在運行時動態生成類型

  我們有了上面的配置後,需要針對模型動態編譯成對應的類。C#提供了多種運行時動態生成類型的方式,下麵我們介紹通過Emit來生成類,上面的配置信息比較適合模型配置信息的管理,對於生成類的話我們又定義了一個方便另外一個類,代碼如下:

  public class TypeMeta
    {
        public TypeMeta()
        {
            PropertyMetas = new List<TypePropertyMeta>();
            AttributeMetas = new List<AttributeMeta>();
        }
        public Type BaseType { get; set; }
        public string TypeName { get; set; }
        public List<TypePropertyMeta> PropertyMetas { get; set; }
        public List<AttributeMeta> AttributeMetas { get; set; }

        public class TypePropertyMeta
        {
            public TypePropertyMeta()
            {
                AttributeMetas = new List<AttributeMeta>();
            }
            public Type PropertyType { get; set; }
            public string PropertyName { get; set; }
            public List<AttributeMeta> AttributeMetas { get; set; }
        }

        public class AttributeMeta
        {
            public Type AttributeType { get; set; }
            public Type[] ConstructorArgTypes { get; set; }
            public object[] ConstructorArgValues { get; set; }
            public string[] Properties { get; set; }
            public object[] PropertyValues { get; set; }
        }
    }

  上面的類信息更接近一個類的定義,我們可以把一個RuntimeModelMeta轉換成一個TypeMeta,我們把這個轉換過程放到IRuntimeModelProvider實現類中,實現代碼如下:

public class DefaultRuntimeModelProvider : IRuntimeModelProvider
    {
        private Dictionary<int, Type> _resultMap;
        private readonly IOptions<RuntimeModelMetaConfig> _config;
        private object _lock = new object();
        public DefaultRuntimeModelProvider(IOptions<RuntimeModelMetaConfig> config)
        {
            //通過依賴註入方式獲取到模型配置信息
            _config = config;
        }
      //動態編譯結果的緩存,這樣在獲取動態類型時不用每次都編譯一次
        public Dictionary<int, Type> Map
        {
            get
            {
                if (_resultMap == null)
                {
                    lock (_lock)
                    {
                        _resultMap = new Dictionary<int, Type>();
                
                        foreach (var item in _config.Value.Metas)
                        {
                            //根據RuntimeModelMeta編譯成類,具體實現看後面內容
                            var result = RuntimeTypeBuilder.Build(GetTypeMetaFromModelMeta(item));
                           //編譯結果放到緩存中,方便下次使用
                            _resultMap.Add(item.ModelId, result);
                        }
                    }
                }
                return _resultMap;
            }
        }

        public Type GetType(int modelId)
        {
            Dictionary<int, Type> map = Map;
            Type result = null;
            if (!map.TryGetValue(modelId, out result))
            {
                throw new NotSupportedException("dynamic model not supported:" + modelId);
            }
            return result;
        }

        public Type[] GetTypes()
        {
            int[] modelIds = _config.Value.Metas.Select(m => m.ModelId).ToArray();
            return Map.Where(m => modelIds.Contains(m.Key)).Select(m => m.Value).ToArray();            
        }
        //這個方法就是把一個RuntimeModelMeta轉換成更接近類結構的TypeMeta對象
        private TypeMeta GetTypeMetaFromModelMeta(RuntimeModelMeta meta)
        {
            TypeMeta typeMeta = new TypeMeta();
            //我們讓所有的動態類型都繼承自DynamicEntity類,這個類主要是為了方便屬性數據的讀取,具體代碼看後面
            typeMeta.BaseType = typeof(DynamicEntity);
            typeMeta.TypeName = meta.ClassName;
           
            foreach (var item in meta.ModelProperties)
            {
                TypeMeta.TypePropertyMeta pmeta = new TypeMeta.TypePropertyMeta();
                pmeta.PropertyName = item.PropertyName;
                //如果必須輸入數據,我們在屬性上增加RequireAttribute特性,這樣方便我們進行數據驗證
                if (item.IsRequired)
                {
                    TypeMeta.AttributeMeta am = new TypeMeta.AttributeMeta();
                    am.AttributeType = typeof(RequiredAttribute);
                    am.Properties = new string[] { "ErrorMessage" };
                    am.PropertyValues = new object[] { "請輸入" + item.Name };
                    pmeta.AttributeMetas.Add(am);
                }
                
                if (item.ValueType == "string")
                {
                    pmeta.PropertyType = typeof(string);
                    TypeMeta.AttributeMeta am = new TypeMeta.AttributeMeta();
                    //增加長度驗證特性
                     am.AttributeType = typeof(StringLengthAttribute);
                     am.ConstructorArgTypes = new Type[] { typeof(int) };
                     am.ConstructorArgValues = new object[] { item.Length };
                     am.Properties = new string[] { "ErrorMessage" };
                     am.PropertyValues = new object[] { item.Name + "長度不能超過" + item.Length.ToString() + "個字元" };
                       
                     pmeta.AttributeMetas.Add(am);
                }
                else if(item.ValueType=="int")
                {
                    if (!item.IsRequired)
                    {
                        pmeta.PropertyType = typeof(int?);
                    }
                    else
                    {
                        pmeta.PropertyType = typeof(int);
                    }
                }
                else if (item.ValueType=="datetime")
                {
                    if (!item.IsRequired)
                    {
                        pmeta.PropertyType = typeof(DateTime?);
                    }
                    else
                    {
                        pmeta.PropertyType = typeof(DateTime);
                    }
                }
                else if (item.ValueType == "bool")
                {
                    if (!item.IsRequired)
                    {
                        pmeta.PropertyType = typeof(bool?);
                    }
                    else
                    {
                        pmeta.PropertyType = typeof(bool);
                    }
                }
                typeMeta.PropertyMetas.Add(pmeta);
            }
            return typeMeta;
        }
    }

  

  DynamicEntity是所有動態類型的基類,主要是方便屬性的操作,具體代碼如下:

public class DynamicEntity: IExtensible
    {
        private Dictionary<object, object> _attrs;
        public DynamicEntity()
        {
            _attrs = new Dictionary<object, object>();
        }
        public DynamicEntity(Dictionary<object,object> dic)
        {
            _attrs = dic;
        }
        public static DynamicEntity Parse(object obj)
        {
            DynamicEntity model = new DynamicEntity();
            foreach (PropertyInfo info in obj.GetType().GetProperties())
            {
                model._attrs.Add(info.Name, info.GetValue(obj, null));
            }
            return model;
        }
        public T GetValue<T>(string field)
        {
            object obj2 = null;
            if(!_attrs.TryGetValue(field, out obj2))
            {
                _attrs.Add(field, default(T));
            }
            if (obj2 == null)
            {
                return default(T);
            }
            return (T)obj2;
        }

        public void SetValue<T>(string field, T value)
        {
            if (_attrs.ContainsKey(field))
            {
                _attrs[field] = value;
            }
            else
            {
                _attrs.Add(field, value);
            }
        }

        [JsonIgnore]
        public Dictionary<object, object> Attrs
        {
            get
            {
                return _attrs;
            }
        }
    //提供索引方式操作屬性值
        public object this[string key]
        {
            get
            {
                object obj2 = null;
                if (_attrs.TryGetValue(key, out obj2))
                {
                    return obj2;
                }
                return null;
            }
            set
            {
                if (_attrs.Any(m => string.Compare(m.Key.ToString(), key, true) != -1))
                {
                    _attrs[key] = value;
                }
                else
                {
                    _attrs.Add(key, value);
                }
            }
        }
        [JsonIgnore]
        public string[] Keys
        {
            get
            {
                return _attrs.Keys.Select(m=>m.ToString()).ToArray();
            }
        }

        public int Id
        {
            get
            {
                return GetValue<int>("Id");
            }
            set
            {
                SetValue("Id", value);
            }
        }
        [Timestamp]
        [JsonIgnore]
        public byte[] Version { get; set; }
    }

  

  另外在上面編譯類的時候用到了RuntimeTypeBuilder類,我們來看下這個類的實現,代碼如下:

public static class RuntimeTypeBuilder
    {
        private static ModuleBuilder moduleBuilder;
        static RuntimeTypeBuilder()
        {
            AssemblyName an = new AssemblyName("__RuntimeType");
            moduleBuilder = AssemblyBuilder.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run).DefineDynamicModule("__RuntimeType");
        }
        public static Type Build(TypeMeta meta)
        {
            TypeBuilder builder = moduleBuilder.DefineType(meta.TypeName, TypeAttributes.Public);
            CustomAttributeBuilder tableAttributeBuilder = new CustomAttributeBuilder(typeof(TableAttribute).GetConstructor(new Type[1] { typeof(string)}), new object[] { "RuntimeModel_" + meta.TypeName });
            builder.SetParent(meta.BaseType);
            builder.SetCustomAttribute(tableAttributeBuilder);
            
            foreach (var item in meta.PropertyMetas)
            {
                AddProperty(item, builder, meta.BaseType);
            }
            return builder.CreateTypeInfo().UnderlyingSystemType;
        }
       
        private static void AddProperty(TypeMeta.TypePropertyMeta property, TypeBuilder builder,Type baseType)
        {
            PropertyBuilder propertyBuilder = builder.DefineProperty(property.PropertyName, PropertyAttributes.None, property.PropertyType, null);

            foreach (var item in property.AttributeMetas)
            {
                if (item.ConstructorArgTypes==null)
                {
                    item.ConstructorArgTypes = new Type[0];
                    item.ConstructorArgValues = new object[0];
                }
                ConstructorInfo cInfo = item.AttributeType.GetConstructor(item.ConstructorArgTypes);
                PropertyInfo[] pInfos = item.Properties.Select(m => item.AttributeType.GetProperty(m)).ToArray();
                CustomAttributeBuilder aBuilder = new CustomAttributeBuilder(cInfo, item.ConstructorArgValues, pInfos, item.PropertyValues);
                propertyBuilder.SetCustomAttribute(aBuilder);
            }

            MethodAttributes attributes = MethodAttributes.SpecialName | MethodAttributes.HideBySig | MethodAttributes.Public;
            MethodBuilder getMethodBuilder = builder.DefineMethod("get_" + property.PropertyName, attributes, property.PropertyType, Type.EmptyTypes);
            ILGenerator iLGenerator = getMethodBuilder.GetILGenerator();
            MethodInfo getMethod = baseType.GetMethod("GetValue").MakeGenericMethod(new Type[] { property.PropertyType });
            iLGenerator.DeclareLocal(property.PropertyType);
            iLGenerator.Emit(OpCodes.Nop);
            iLGenerator.Emit(OpCodes.Ldarg_0);
            iLGenerator.Emit(OpCodes.Ldstr, property.PropertyName);
            iLGenerator.EmitCall(OpCodes.Call, getMethod, null);
            iLGenerator.Emit(OpCodes.Stloc_0);
            iLGenerator.Emit(OpCodes.Ldloc_0);
            iLGenerator.Emit(OpCodes.Ret);
            MethodInfo setMethod = baseType.GetMethod("SetValue").MakeGenericMethod(new Type[] { property.PropertyType });
            MethodBuilder setMethodBuilder = builder.DefineMethod("set_" + property.PropertyName, attributes, null, new Type[] { property.PropertyType });
            ILGenerator generator2 = setMethodBuilder.GetILGenerator();
            generator2.Emit(OpCodes.Nop);
            generator2.Emit(OpCodes.Ldarg_0);
            generator2.Emit(OpCodes.Ldstr, property.PropertyName);
            generator2.Emit(OpCodes.Ldarg_1);
            generator2.EmitCall(OpCodes.Call, setMethod, null);
            generator2.Emit(OpCodes.Nop);
            generator2.Emit(OpCodes.Ret);
            propertyBuilder.SetGetMethod(getMethodBuilder);
            propertyBuilder.SetSetMethod(setMethodBuilder);

        }


    }

  主要部分是ILGenerator的使用,具體使用方式大家可以查閱相關資料,這裡不再詳細介紹。

  三、如何讓ef識別動態類型

  在ef中操作對象需要藉助DbContext,如果靜態的類型,那我們就可以在定義DbContext的時候,增加DbSet<TEntity>類型的屬性即可,但是我們現在的類型是在運行時生成的,那怎麼樣才能讓DbContext能夠認識這個類型,答案是OnModelCreating方法,在這個方法中,我們把動態模型加入到DbContext中,具體方式如下:

  protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
       //_modelProvider就是我們上面定義的IRuntimeModelProvider,通過依賴註入方式獲取到實例
            Type[] runtimeModels = _modelProvider.GetTypes("product");
            foreach (var item in runtimeModels)
            {
                modelBuilder.Model.AddEntityType(item);
            }

            base.OnModelCreating(modelBuilder);
        }

  

  這樣在我們DbContext就能夠識別動態類型了。註冊到DbContext很簡單,關鍵是如何進行信息的操作。

  四、如何結合ef對動態信息進行操作

  我們先把上面的DbContext類補充完整,

  public class ShopDbContext : DbContext
    {
        private readonly IRuntimeModelProvider _modelProvider;
        public ShopDbContext(DbContextOptions<ShopDbContext> options, IRuntimeModelProvider modelProvider)
            : base(options)
        {
            _modelProvider = modelProvider;
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            Type[] runtimeModels = _modelProvider.GetTypes("product");
            foreach (var item in runtimeModels)
            {
                modelBuilder.Model.AddEntityType(item);
            }

            base.OnModelCreating(modelBuilder);
        }
}

  

  在efcore中對象的增加,刪除,更新可以直接使用DbContext就可以完成,比如增加代碼,

ShopDbContext.Add(entity);
ShopDbContext.SaveChanges();

  更新操作比較簡單,比較難解決的是查詢,包括查詢條件設置等等。國外有大牛寫了一個LinqDynamic,我又對它進行了修改,並增加了一些非同步方法,代碼我就不粘貼到文章里了,大家可以直接下載源碼:下載linqdynamic

  LinqDynamic中是對IQueryable的擴展,提供了動態linq的查詢支持,具體使用方法大家可以百度。efcore中DbSet泛型定義如下:

  

  public abstract partial class DbSet<TEntity>: IQueryable<TEntity>, IAsyncEnumerableAccessor<TEntity>, IInfrastructure<IServiceProvider>

  不難發現,它就是一個IQueryable<TEntity>,而IQueryable<TEntity>又是一個IQueryable,正好是LinqDynamic需要的類型,所以我們現在需要解決的是根據動態模型信息,獲取到一個IQueryable,我採用反射方式獲取:

   ShopDbContext.GetType().GetTypeInfo().GetMethod("Set").MakeGenericMethod(type).Invoke(context, null) as IQueryable;

  有了IQueryable,就可以使用LinqDynamic增加的擴展方式,實現動態查詢了。查詢到的結果是一個動態類型,但是我們前面提到,我們所有的動態類型都是一個DynamicEntity類型,所以我們要想訪問某個屬性的值的時候,我們可以直接採用索引的方式讀取,比如obj["屬性"],然後結合RuntimeModelMeta配置信息,就可以動態的把數據呈現到頁面上了。

  上面的方案還可以繼續改進,可以把配置信息保存到資料庫中,在程式中增加模型配置管理的功能,實現線上的模型配置,配置改動可以同步操作資料庫表結構,這種方案後續補充上,敬請期待。


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

-Advertisement-
Play Games
更多相關文章
  • 第一步:搭建apache伺服器,這在上篇博客中已經完成 http://www.cnblogs.com/sangmu/p/6422238.html 第二步:安裝ssl yum install mod_ssl -y iptables -I INPUT 1 -p tcp --dport 443 -j AC ...
  • 昨天Boss在公司微信群發了此張圖片,"哪個水槽先灌滿",Insus.NET今天早上才有時間來研究一下: 如果你對此話題有興趣,你可以繼續討論和補充...... ...
  • 前述: 對資料庫操作的封裝,相信網路上已經有一大堆,ORM框架,或者是.NET本身的EF,都很好的支持資料庫操作。這篇文章是分享自己所思考的,對資料庫操作的簡單封裝。我對於這篇文章,認為被瀏覽者所關註重點的是怎麼分析設計資料庫操作封裝,代碼是其次。而且,這是我第一篇文章,為了想好怎麼實現花了些天,代 ...
  • 年前經常聊天的大佬群里有人寫了窗體的倒計時來計算下班時間和放假時間:) 簡直就是在嘲諷我這種沒有工作的人,哈哈哈 窗體的倒計時相當的沒有技術含量,主要是不夠炫酷,不能夠體現我們程式員的身份。 那什麼才叫炫酷?必須是控制台啊! 電視劇上黑客噼里啪啦噼里啪啦滾屏的畫面多炫酷! 所以,研究了一下怎麼樣在控 ...
  • 適用Zero版本:ASP.NET Core & Angular 2+ (aspnet-zero-core-3.1.0)。 該版本官方有兩個solution文件夾:Angular(前端) 和 aspnet-core(後臺服務)。 在開始以下步驟之前需要能夠成功發佈程式,對於後臺服務只要能運行即可,如有 ...
  • 註釋,是代碼中的一些“說明性文字”。註釋本身不會參與程式的編譯和運行,僅僅供程式員閱讀。 註釋分為:單行註釋、多行註釋、文檔註釋。 單行註釋的符號是2條斜線“//”,2條斜線右側的內容就是註釋,左側的代碼不會受影響。 多行註釋以“/*”開始,以“*/”結束,之間的內容就是註釋,可以包含多行。 文檔註 ...
  • OSS最新進度,包括OSS.Social,OSS.Http,新增微信支付項目OSS.PayCenter。 ...
  • 簡介 RabbitMQ:一個消息系統,基於 AMQP 系統協議,由 erlang 語言開發。 優點:健壯、使用簡單、開源和支持各種流行的語言等。 MQ(Message Queue):消息隊列的簡稱,是一種應用程式之間的通信機制。 作用:將部分無需立即回調獲取結果,並且耗時的操作,使用非同步處理的方式提 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...