使用roslyn代替MSBuild完成解決方案編譯

来源:http://www.cnblogs.com/VAllen/archive/2016/03/10/use-roslyn-build-project.html
-Advertisement-
Play Games

經過上面的折騰,我成功用批處理編譯 .NET Framework 4.5.2 項目後, 我並未滿足, 我想要更方便的, 無須安裝那麼多操蛋的東西, 只需要有運行時環境就可以了, 行不行? 答案當然是可以的, 那便是近年漸火的 roslyn 開源項目.


原本我是使用批處理調用 MSBuild 完成解決方案編譯的,新版的 MSBuild 在 Visual Studio 2015 會自帶安裝.

當然在Visual Studio 2015 中,MSBuild 是一個獨立的安裝包,可以單獨安裝,而無須安裝 Visual Studio 2015.

剛開始,我在 Windows Server 2008 R2 上使用 MSBuild 編譯使用 .NET Framework 4.5.2 版本 開發的項目,也不是那麼順利的.

期間,遇到並且解決了很多問題,依次順序為:

1. Windows Server 2008 R2 沒有安裝 .NET Framework 4.5.2 ,這個安裝 .NET Framework 4.5.2 就解決了.

2. Windows Server 2008 R2 上沒有可以編譯 .NET Framework 4.5.2 版本項目的  MSBuild .

    原因是 MSBuild 的版本問題,因為 .NET Framework 4.0 自帶的 MSBuild 不能識別 C# 6.0 語法特性.

    對於這個問題,當時很糾結,因為那時我還不知道 MSBuild 有了獨立安裝包,以為想要用新版的 MSBuild 必須在伺服器上安裝 Visual Studio 2015.

    後來我在visualstudio.com上找到了 MSBuild 獨立安裝包, 名為 Microsoft Build Tools 2015, 所以這個問題也算是解決了.

3. 在Windows Server 2008 R2 上用 MSBuild 2015 居然要安裝 .NET Framework 4.5.2 SDK ?

    這個也是安裝 .NET Framework 4.5.2 Developer Pack 就可以解決了, 不過我沒安裝, 而是直接從本機上拷貝一份到伺服器上, 存放位置和本機的路徑一樣.

    只要安裝了 Visual Studio 2015 ,那麼 SDK 的位置一般在(x86系統)C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.2 ,

    (x64系統)C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.2.

4. 由於在項目中引用了第三方組件,而第三方組件又引用了一些 .NET Framework 4.0 的 dll, 而在引用第三方組件的項目中沒有引用第三方組件中引用了的 .NET Framework 4.0 的 dll.

    一般情況下,用 Visual Studio 2015 進行編譯是沒有問題的. 當使用批處理進行編譯的時候, 問題就來了, 拋出了一對錯誤, 諸如 System.Object, Object 之類的錯誤, 比如:

    error CS0012: The type 'Object' is defined in an assembly that is not referenced.
    You must add a reference to assembly 'System.Runtime, Version=4.0.0.0, Culture=neutral,
    PublicKeyToken=b03f5f7f11d50a3a'.

    The type 'System.Object' is defined in an assembly
    that is not referenced. You must add a reference to assembly 'System.Runtime,
    Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'.

    發現同一依賴程式集的不同版本之間存在衝突

    解決辦法就是在引用了第三方組件的項目中, 引用第三方組件中引用了的 .NET Framework 4.0 的 dll. 這樣批處理是可以成功執行完成編譯的了. 但 Visual Studio 2015 編譯卻報錯了.

    於是折騰了一番, 敲定解決辦法是:

        1.拷貝System.Runtime.dll到解決方案目錄(隨意, 我的是Library目錄)下.

        2.直接打開需要引用的csproj文件,向其中添加:

<Reference Include="System.Runtime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
    <HintPath>..\..\Library\System.Runtime.dll</HintPath>
    <Private>True</Private>
</Reference>

        3.向 Web.Config 的 runtime --> assemblyBinding 節添加:

<dependentAssembly>
<assemblyIdentity name="System.Runtime" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
</dependentAssembly>

    到這裡, VS 編譯 和 批處理編譯都沒問題了.

 

MSBuild 批處理編譯 .NET Framework 4.5.2 項目的命令行

ECHO 初始化變數
SET ProgramPath="C:\Program Files"
IF EXIST %windir%\SysWOW64 SET ProgramPath="C:\Program Files (x86)"
SET MsBuildExe="%ProgramPath%\MSBuild\14.0\Bin\MSBuild.exe" /t:rebuild /verbosity:q /p:Configuration=Release;FrameworkPathOverride="%ProgramPath%\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.2" /l:FileLogger,Microsoft.Build.Engine;verbosity=normal;encoding=utf-8;append=true;logfile=Build.log

ECHO 編譯解決方案
%MsBuildExe% "C:\work\CRM\CRM.VS2015.sln"

大功告成, 這樣就可以編譯 .NET Framework 4.5.2 項目了.

可是後來我為什麼要換成 roslyn 編譯呢? 這也是有原因的!

經過上面的折騰,我成功用批處理編譯 .NET Framework 4.5.2 項目後, 我並未滿足, 我想要更方便的, 無須安裝那麼多操蛋的東西, 只需要有運行時環境就可以了, 行不行?

答案當然是可以的, 那便是近年漸火的 roslyn 開源項目.

 接下來, 我嘗試使用 roslyn 幫助我完成編譯 .NET Framework 4.5.2 項目.

首先, 用 Visual Studio 2015 Update 1 新建一個目標框架為 .NET Framework 4.5.2 的控制台 C# 項目, 為什麼一定要 Update 1, 不解釋, 照做就對了.

        然後對這項目右鍵屬性, 更改程式集名稱為 RBuild .

其次, 在 Visual Studio 菜單欄中 工具 --> NuGet 包管理器  --> 程式包管理器控制台.

    在控制台輸入指令: Install-Package Microsoft.CodeAnalysis 以及 Install-Package Microsoft.Net.Compilers.

    安裝成功後,在項目中會有個包管理文件 packages.config

內容如下:

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Microsoft.CodeAnalysis" version="1.1.1" targetFramework="net452" />
  <package id="Microsoft.CodeAnalysis.Analyzers" version="1.1.0" targetFramework="net452" />
  <package id="Microsoft.CodeAnalysis.Common" version="1.1.1" targetFramework="net452" />
  <package id="Microsoft.CodeAnalysis.CSharp" version="1.1.1" targetFramework="net452" />
  <package id="Microsoft.CodeAnalysis.CSharp.Workspaces" version="1.1.1" targetFramework="net452" />
  <package id="Microsoft.CodeAnalysis.VisualBasic" version="1.1.1" targetFramework="net452" />
  <package id="Microsoft.CodeAnalysis.VisualBasic.Workspaces" version="1.1.1" targetFramework="net452" />
  <package id="Microsoft.CodeAnalysis.Workspaces.Common" version="1.1.1" targetFramework="net452" />
  <package id="Microsoft.Composition" version="1.0.27" targetFramework="net452" />
  <package id="Microsoft.Net.Compilers" version="1.1.1" targetFramework="net452" developmentDependency="true" />
  <package id="System.Collections" version="4.0.0" targetFramework="net452" />
  <package id="System.Collections.Immutable" version="1.1.37" targetFramework="net452" />
  <package id="System.Diagnostics.Debug" version="4.0.0" targetFramework="net452" />
  <package id="System.Globalization" version="4.0.0" targetFramework="net452" />
  <package id="System.IO" version="4.0.0" targetFramework="net452" />
  <package id="System.Linq" version="4.0.0" targetFramework="net452" />
  <package id="System.Reflection" version="4.0.0" targetFramework="net452" />
  <package id="System.Reflection.Extensions" version="4.0.0" targetFramework="net452" />
  <package id="System.Reflection.Metadata" version="1.1.0" targetFramework="net452" />
  <package id="System.Reflection.Primitives" version="4.0.0" targetFramework="net452" />
  <package id="System.Resources.ResourceManager" version="4.0.0" targetFramework="net452" />
  <package id="System.Runtime" version="4.0.0" targetFramework="net452" />
  <package id="System.Runtime.Extensions" version="4.0.0" targetFramework="net452" />
  <package id="System.Runtime.InteropServices" version="4.0.0" targetFramework="net452" />
  <package id="System.Text.Encoding" version="4.0.0" targetFramework="net452" />
  <package id="System.Text.Encoding.Extensions" version="4.0.0" targetFramework="net452" />
  <package id="System.Threading" version="4.0.0" targetFramework="net452" />
</packages>

接著, 在 Program類 敲代碼:

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.MSBuild;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;

namespace WebAPI.Build.Roslyn
{
    class Program
    {
        /// <summary>
        /// 待寫入文件的log列表
        /// </summary>
        static List<string> Logs = new List<string>();

        /// <summary>
        /// 輸出文件,成功與否
        /// </summary>
        static Dictionary<string, bool> OutputFiles = new Dictionary<string, bool>();

        static void Main(string[] args)
        {
            //命令行參數解析器
            CommandLineArgumentParser arguments = CommandLineArgumentParser.Parse(args);

            if (arguments.Has(ConfigInfo.Help) || arguments.Has(ConfigInfo.Question))
            {
                string helpFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "help.txt");
                string[] contents = File.ReadAllLines(helpFile);
                foreach (string content in contents)
                {
                    Console.WriteLine(content);
                }

                return;
            }

            //解決方案路徑
            string solutionUrl;
            if (arguments.Has(ConfigInfo.SolutionUrl))
            {
                solutionUrl = arguments.Get(ConfigInfo.SolutionUrl).Next;
            }
            else
            {
                solutionUrl = GetAppSetting(ConfigInfo.SolutionUrl);
            }

            //輸出目錄
            string outputDir;
            if (arguments.Has(ConfigInfo.OutputDir))
            {
                outputDir = arguments.Get(ConfigInfo.OutputDir).Next;
            }
            else
            {
                outputDir = GetAppSetting(ConfigInfo.OutputDir);
            }

            //編譯屬性
            string properties;
            if (arguments.Has(ConfigInfo.Properties))
            {
                properties = arguments.Get(ConfigInfo.Properties).Next;
            }
            else
            {
                properties = GetAppSetting(ConfigInfo.Properties);
            }

            Dictionary<string, string> keyValues;
            if (!string.IsNullOrEmpty(properties))
            {
                keyValues = new Dictionary<string, string>();
                IEnumerable<string> props = properties.Split(';').Where(t => !string.IsNullOrWhiteSpace(t));
                foreach (var item in props)
                {
                    string[] prop = item.Split('=');
                    keyValues.Add(prop[0], prop[1]);
                }
            }
            else
            {
                keyValues = null;
            }

            string logFile;
            if (arguments.Has(ConfigInfo.LogFile))
            {
                logFile = arguments.Get(ConfigInfo.LogFile).Next;
            }
            else
            {
                logFile = GetAppSetting(ConfigInfo.LogFile);
            }

            if (!File.Exists(solutionUrl))
            {
                AddFormatPrint("The file specified does not exist.");
                AddFormatPrint("FileName:" + solutionUrl);
            }
            else
            {
                AddFormatPrint("Start building solutions");
                AddFormatPrint();

                AddFormatPrint("Check output directory exists");
                if (!Directory.Exists(outputDir))
                {
                    AddFormatPrint("Create output directory:");
                    AddFormatPrint(outputDir);
                    Directory.CreateDirectory(outputDir);
                    AddFormatPrint("Output directory has been created successfully");
                }
                else
                {
                    AddFormatPrint("Output directory already exists");
                }
                AddFormatPrint();

                AddFormatPrint("Start compilation solution");
                AddFormatPrint();
                bool success = CompileSolution(solutionUrl, outputDir, keyValues);
                AddFormatPrint();

                if (success)
                {
                    AddFormatPrint("Compilation completed successfully.");
                }
                else
                {
                    AddFormatPrint("Compilation failed.");
                }
            }

            foreach (string fullPathName in OutputFiles.Where(t => t.Value == false).Select(t => t.Key))
            {
                try
                {
                    File.Delete(fullPathName);
                }
                catch
                {
                }
            }
            File.WriteAllLines(logFile, Logs);

#if DEBUG
            AddFormatPrint("Press the any key to exit.");
            Console.ReadKey();
#endif
        }

        /// <summary>
        /// 編譯解決方案和輸出項目bin文件
        /// </summary>
        /// <param name="solutionUrl"></param>
        /// <param name="outputDir"></param>
        /// <param name="keyValues"></param>
        /// <returns></returns>
        private static bool CompileSolution(string solutionUrl, string outputDir, Dictionary<string, string> keyValues = null)
        {
            bool success = true;

            MSBuildWorkspace workspace;
            if (keyValues != null && keyValues.Any())
            {
                workspace = MSBuildWorkspace.Create(keyValues);
            }
            else
            {
                workspace = MSBuildWorkspace.Create();
            }

            Solution solution = workspace.OpenSolutionAsync(solutionUrl).Result;

            ProjectDependencyGraph projectGraph = solution.GetProjectDependencyGraph();
            foreach (ProjectId projectId in projectGraph.GetTopologicallySortedProjects())
            {
                Project project = solution.GetProject(projectId);
                AddFormatPrint("Building: {0}", project.FilePath);
                try
                {
                    Compilation projectCompilation = project.GetCompilationAsync().Result;
                    if (null != projectCompilation && !string.IsNullOrEmpty(projectCompilation.AssemblyName))
                    {
                        string fileName = string.Format("{0}.dll", projectCompilation.AssemblyName);
                        string fullPathName = string.Format("{0}\\{1}", outputDir, fileName);
                        if (!OutputFiles.ContainsKey(fullPathName))
                        {
                            OutputFiles.Add(fullPathName, true);
                        }

                        var diagnostics = projectCompilation.GetDiagnostics();
                        var warnDiagnostics = diagnostics.Where(x => x.Severity == DiagnosticSeverity.Warning).ToArray();
                        var errorDiagnostics = diagnostics.Where(x => x.Severity == DiagnosticSeverity.Error).ToArray();

                        foreach (var e in errorDiagnostics.Concat(warnDiagnostics).ToArray())
                        {
                            AddFormatPrint("{0}: {1}", e.Severity.ToString(), e.ToString());
                        }

                        if (errorDiagnostics.Any())
                        {
                            OutputFiles[fullPathName] = false;
                            AddFormatPrint("Build failed.");
                            success = false;
                        }
                        else
                        {
                            AddFormatPrint("Build successfully.");

                            using (var stream = new MemoryStream())
                            {
                                EmitResult result = projectCompilation.Emit(stream);
                                AddFormatPrint("{0}  -->  {1}", project.Name, fullPathName);
                                if (result.Success)
                                {
                                    using (FileStream file = File.Create(fullPathName))
                                    {
                                        stream.Seek(0, SeekOrigin.Begin);
                                        stream.CopyTo(file);
                                    }
                                    AddFormatPrint("Output successfully.");
                                }
                                else
                                {
                                    OutputFiles[fullPathName] = false;
                                    AddFormatPrint("Output failed.");
                                    success = false;
                                }
                            }
                        }
                        AddFormatPrint();
                    }
                    else
                    {
                        AddFormatPrint("Build failed. {0}", project.FilePath);
                        success = false;
                    }
                }
                catch (AggregateException ex)
                {
                    foreach (var ie in ex.InnerExceptions)
                    {
                        AddFormatPrint(ie.Message);
                    }
                    success = false;
                }
                catch (Exception ex)
                {
                    AddFormatPrint(ex.Message);
                    success = false;
                }
                AddFormatPrint();
            }

            return success;
        }

        /// <summary>
        /// 添加消息記錄和列印消息
        /// </summary>
        /// <param name="format"></param>
        /// <param name="args"></param>
        private static void AddFormatPrint(string format = "", params object[] args)
        {
            if (format == string.Empty)
            {
                Logs.Add(string.Empty);
                Console.WriteLine();
            }
            else
            {
                string log = string.Format(format, args);
                Logs.Add(log);
                Console.WriteLine(log);
            }
        }

        /// <summary>
        /// 獲取配置值
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        private static string GetAppSetting(string key)
        {
            return ConfigurationManager.AppSettings[key] ?? ConfigurationManager.AppSettings[ConfigInfo.KeyValues[key]];
        }
    }

    public struct ConfigInfo
    {
        /// <summary>
        /// 解決方案路徑
        /// </summary>
        public const string SolutionUrl = "-s";

        /// <summary>
        /// 輸出目錄
        /// </summary>
        public const string OutputDir = "-o";

        /// <summary>
        /// 編譯屬性
        /// </summary>
        public const string Properties = "-p";

        /// <summary>
        /// 日誌文件名稱
        /// </summary>
        public const string LogFile = "-l";

        /// <summary>
        /// 幫助
        /// </summary>
        public const string Help = "-h";

        /// <summary>
        /// 提問
        /// </summary>
        public const string Question = "-?";

        /// <summary>
        /// 全稱鍵值對
        /// </summary>
        public static readonly Dictionary<string, string> KeyValues = new Dictionary<string, string> { { SolutionUrl, "solutionUrl" }, { OutputDir, "outputDir" }, { Properties, "properties" }, { LogFile, "logFile" } };
    }
}

編譯完成後, 就可以在 CMD 命令行提示符用了.

這是 help.txt 內容以及用法:

Provide help information for commands.

RBuild  [-s] [-o] [-p]

        -s —— Solution Path, required. 
                Use: -s "C:\work\CRM\CRM.VS2015.sln"
        -o —— Output Directory, required. 
                Use: -o "E:\work\CRM\Build\WebAPI\bin"
        -p —— Build Properties, required. 
                Use: -p Configuration=Release;FrameworkPathOverride=C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.2;key=value
        -l —— Output Log Path, not required. 
                Use: -l "log.txt"

目前我只知道 roslyn 可以編譯項目, 但編譯後拷貝相關的非代碼文件到相應目錄這個功能卻沒有發現哪裡有, 只能通過批處理自己拷貝了.

我的用法

ECHO 開始編譯解決方案
BuildTools\RBuild.exe -s "..\..\CRM\src\CRM.VS2015.sln" -o "CRM\bin" -p Configuration=Release -l Build.log

 

安裝包下載:

.NET Framework 4.5.2(運行時環境):https://www.microsoft.com/zh-cn/download/details.aspx?id=42642

.NET Framework 4.5.2 Language Pack(可選):https://www.microsoft.com/zh-cn/download/details.aspx?id=42641

.NET Framework 4.5.2 Developer Pack(SDK):https://www.microsoft.com/zh-CN/download/details.aspx?id=42637

Microsoft Build Tools 2015(MSBuild):https://www.microsoft.com/zh-cn/download/details.aspx?id=48159

 

引用資料:

http://www.cnblogs.com/walkerwang/p/3368986.html

http://yangpei.appsp0t.com/post/aglzfnlhbmdwZWlyDAsSBUVudHJ5GKEfDA

http://stackoverflow.com/questions/13280008/how-do-i-compile-a-c-sharp-solution-with-roslyn

http://www.cnblogs.com/linxuanchen/p/c-sharp-command-line-argument-parser.html

https://msdn.microsoft.com/zh-cn/library/ms164311.aspx

 https://github.com/dotnet/roslyn

 


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

-Advertisement-
Play Games
更多相關文章
  • 最近項目不忙,想著沒事看看簡單的嵌入式,弄弄物聯網什麼的。於是就從廉價的STM32開刀了。因為一直是做PC軟體開發的,那VS的智能感知那叫一個爽啊,相比之下,覺得這個Keil簡直就像文本編輯器一樣low。於是想換一個開發環境,就把矛頭指向了Eclipse,以前PC開發也正好用過。(其實拿他寫C++也
  • 最近在弄ejabberd+riak。其實這倆東西配置挺坑的,然後具體這倆貨怎麼配置,我以後會抽空寫出配置的詳細過程。對於負載均衡,我知道的現在有Nginx/LVS/HAProxy這三個大仙兒,各自有各自的優缺點,有關優缺點大家可以度娘一下。先來看看什麼是負載均衡。 負載均衡:是由多台伺服器以對稱的方
  • 一般簡單的樣式我們可以直接寫在控制項中 <Button Canvas.Left="20" Canvas.Top="30" Width="100" Height="120" Content="A"/> 也可以通過Style屬性定義該系列控制項的樣式 <Style TargetType="{x:Type B
  • 1. 8K 對應的SQL語句限制 select * from openquery (recei 連接伺服器名稱 執行的sql 語句放在 SELECT @@SERVERNAME 在本地進行執行 2. 使用 工具進行搭建主從伺服器 和對應的同步複製伺服器 1.SQL Server Proxy Servi
  • 集合(collection)提供了一種結構化組織任意對象的方式,從廣義的概念上講,數組、枚舉和結構等組合都是集合的一種表現,其內部的元素組織方式也都與集合的定義非常類似。但在C#中,集合這一專有名詞特指System.Collections命名空間下的各種子類,數組、枚舉和結構都不是System.Co
  • 根據大牛的方案修改的自己使用的Actor!
  • 關於C# webform 項目發佈 註意:aspx頁面無任何代碼,每個頁面都以dll形式發佈帶bin文件夾里 具體操作方法如下圖:
  • Line 在兩個坐標點之間畫一條直線,通過四個屬性設置它的起始和結束 <Line Stroke="Blue" StrokeThickness="3" X1="20" Y1="20" X2="300" Y2="20"></Line> 如果線條是畫在Canvas畫布中,那麼Canvas的附加屬性Top和
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...