一個c#的與web伺服器交互的HttpClient類

来源:https://www.cnblogs.com/tewuapple/archive/2017/12/27/8124774.html
-Advertisement-
Play Games

using System;using System.Collections.Generic;using System.IO;using System.Text;using System.Net;using System.Web; namespace Deerchao.Utility{ public ...


using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Net;
using System.Web;

namespace Deerchao.Utility
{
    public class HttpClient
    {
        #region fields
        private bool keepContext;
        private string defaultLanguage = "zh-CN";
        private Encoding defaultEncoding = Encoding.UTF8;
        private string accept = "*/*";
        private string userAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)";
        private HttpVerb verb = HttpVerb.GET;
        private HttpClientContext context;
        private readonly List<HttpUploadingFile> files = new List<HttpUploadingFile>();
        private readonly Dictionary<string, string> postingData = new Dictionary<string, string>();
        private string url;
        private WebHeaderCollection responseHeaders;
        private int startPoint;
        private int endPoint;
        #endregion

        #region events
        public event EventHandler<StatusUpdateEventArgs> StatusUpdate;

        private void OnStatusUpdate(StatusUpdateEventArgs e)
        {
            EventHandler<StatusUpdateEventArgs> temp = StatusUpdate;

            if (temp != null)
                temp(this, e);
        }
        #endregion

        #region properties
        /// <summary>
        /// 是否自動在不同的請求間保留Cookie, Referer
        /// </summary>
        public bool KeepContext
        {
            get { return keepContext; }
            set { keepContext = value; }
        }

        /// <summary>
        /// 期望的回應的語言
        /// </summary>
        public string DefaultLanguage
        {
            get { return defaultLanguage; }
            set { defaultLanguage = value; }
        }

        /// <summary>
        /// GetString()如果不能從HTTP頭或Meta標簽中獲取編碼信息,則使用此編碼來獲取字元串
        /// </summary>
        public Encoding DefaultEncoding
        {
            get { return defaultEncoding; }
            set { defaultEncoding = value; }
        }

        /// <summary>
        /// 指示發出Get請求還是Post請求
        /// </summary>
        public HttpVerb Verb
        {
            get { return verb; }
            set { verb = value; }
        }

        /// <summary>
        /// 要上傳的文件.如果不為空則自動轉為Post請求
        /// </summary>
        public List<HttpUploadingFile> Files
        {
            get { return files; }
        }

        /// <summary>
        /// 要發送的Form表單信息
        /// </summary>
        public Dictionary<string, string> PostingData
        {
            get { return postingData; }
        }

        /// <summary>
        /// 獲取或設置請求資源的地址
        /// </summary>
        public string Url
        {
            get { return url; }
            set { url = value; }
        }

        /// <summary>
        /// 用於在獲取回應後,暫時記錄回應的HTTP頭
        /// </summary>
        public WebHeaderCollection ResponseHeaders
        {
            get { return responseHeaders; }
        }

        /// <summary>
        /// 獲取或設置期望的資源類型
        /// </summary>
        public string Accept
        {
            get { return accept; }
            set { accept = value; }
        }

        /// <summary>
        /// 獲取或設置請求中的Http頭User-Agent的值
        /// </summary>
        public string UserAgent
        {
            get { return userAgent; }
            set { userAgent = value; }
        }

        /// <summary>
        /// 獲取或設置Cookie及Referer
        /// </summary>
        public HttpClientContext Context
        {
            get { return context; }
            set { context = value; }
        }

        /// <summary>
        /// 獲取或設置獲取內容的起始點,用於斷點續傳,多線程下載等
        /// </summary>
        public int StartPoint
        {
            get { return startPoint; }
            set { startPoint = value; }
        }

        /// <summary>
        /// 獲取或設置獲取內容的結束點,用於斷點續傳,多下程下載等.
        /// 如果為0,表示獲取資源從StartPoint開始的剩餘內容
        /// </summary>
        public int EndPoint
        {
            get { return endPoint; }
            set { endPoint = value; }
        }

        #endregion

        #region constructors
        /// <summary>
        /// 構造新的HttpClient實例
        /// </summary>
        public HttpClient()
            : this(null)
        {
        }

        /// <summary>
        /// 構造新的HttpClient實例
        /// </summary>
        /// <param name="url">要獲取的資源的地址</param>
        public HttpClient(string url)
            : this(url, null)
        {
        }

        /// <summary>
        /// 構造新的HttpClient實例
        /// </summary>
        /// <param name="url">要獲取的資源的地址</param>
        /// <param name="context">Cookie及Referer</param>
        public HttpClient(string url, HttpClientContext context)
            : this(url, context, false)
        {
        }

        /// <summary>
        /// 構造新的HttpClient實例
        /// </summary>
        /// <param name="url">要獲取的資源的地址</param>
        /// <param name="context">Cookie及Referer</param>
        /// <param name="keepContext">是否自動在不同的請求間保留Cookie, Referer</param>
        public HttpClient(string url, HttpClientContext context, bool keepContext)
        {
            this.url = url;
            this.context = context;
            this.keepContext = keepContext;
            if (this.context == null)
                this.context = new HttpClientContext();
        }
        #endregion

        #region AttachFile
        /// <summary>
        /// 在請求中添加要上傳的文件
        /// </summary>
        /// <param name="fileName">要上傳的文件路徑</param>
        /// <param name="fieldName">文件欄位的名稱(相當於&lt;input type=file name=fieldName&gt;)里的fieldName)</param>
        public void AttachFile(string fileName, string fieldName)
        {
            HttpUploadingFile file = new HttpUploadingFile(fileName, fieldName);
            files.Add(file);
        }

        /// <summary>
        /// 在請求中添加要上傳的文件
        /// </summary>
        /// <param name="data">要上傳的文件內容</param>
        /// <param name="fileName">文件名</param>
        /// <param name="fieldName">文件欄位的名稱(相當於&lt;input type=file name=fieldName&gt;)里的fieldName)</param>
        public void AttachFile(byte[] data, string fileName, string fieldName)
        {
            HttpUploadingFile file = new HttpUploadingFile(data, fileName, fieldName);
            files.Add(file);
        }
        #endregion

        /// <summary>
        /// 清空PostingData, Files, StartPoint, EndPoint, ResponseHeaders, 並把Verb設置為Get.
        /// 在發出一個包含上述信息的請求後,必須調用此方法或手工設置相應屬性以使下一次請求不會受到影響.
        /// </summary>
        public void Reset()
        {
            verb = HttpVerb.GET;
            files.Clear();
            postingData.Clear();
            responseHeaders = null;
            startPoint = 0;
            endPoint = 0;
        }

        private HttpWebRequest CreateRequest()
        {
            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
            req.AllowAutoRedirect = false;
            req.CookieContainer = new CookieContainer();
            req.Headers.Add("Accept-Language", defaultLanguage);
            req.Accept = accept;
            req.UserAgent = userAgent;
            req.KeepAlive = false;

            if (context.Cookies != null)
                req.CookieContainer.Add(context.Cookies);
            if (!string.IsNullOrEmpty(context.Referer))
                req.Referer = context.Referer;

            if (verb == HttpVerb.HEAD)
            {
                req.Method = "HEAD";
                return req;
            }

            if (postingData.Count > 0 || files.Count > 0)
                verb = HttpVerb.POST;

            if (verb == HttpVerb.POST)
            {
                req.Method = "POST";

                MemoryStream memoryStream = new MemoryStream();
                StreamWriter writer = new StreamWriter(memoryStream);

                if (files.Count > 0)
                {
                    string newLine = "\r\n";
                    string boundary = Guid.NewGuid().ToString().Replace("-", "");
                    req.ContentType = "multipart/form-data; boundary=" + boundary;

                    foreach (string key in postingData.Keys)
                    {
                        writer.Write("--" + boundary + newLine);
                        writer.Write("Content-Disposition: form-data; name=\"{0}\"{1}{1}", key, newLine);
                        writer.Write(postingData[key] + newLine);
                    }

                    foreach (HttpUploadingFile file in files)
                    {
                        writer.Write("--" + boundary + newLine);
                        writer.Write(
                            "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"{2}",
                            file.FieldName,
                            file.FileName,
                            newLine
                            );
                        writer.Write("Content-Type: application/octet-stream" + newLine + newLine);
                        writer.Flush();
                        memoryStream.Write(file.Data, 0, file.Data.Length);
                        writer.Write(newLine);
                        writer.Write("--" + boundary + newLine);
                    }
                }
                else
                {
                    req.ContentType = "application/x-www-form-urlencoded";
                    StringBuilder sb = new StringBuilder();
                    foreach (string key in postingData.Keys)
                    {
                        sb.AppendFormat("{0}={1}&", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(postingData[key]));
                    }
                    if (sb.Length > 0)
                        sb.Length--;
                    writer.Write(sb.ToString());
                }

                writer.Flush();

                using (Stream stream = req.GetRequestStream())
                {
                    memoryStream.WriteTo(stream);
                }
            }

            if (startPoint != 0 && endPoint != 0)
                req.AddRange(startPoint, endPoint);
            else if (startPoint != 0 && endPoint == 0)
                req.AddRange(startPoint);

            return req;
        }

        /// <summary>
        /// 發出一次新的請求,並返回獲得的回應
        /// 調用此方法永遠不會觸發StatusUpdate事件.
        /// </summary>
        /// <returns>相應的HttpWebResponse</returns>
        public HttpWebResponse GetResponse()
        {
            HttpWebRequest req = CreateRequest();
            HttpWebResponse res = (HttpWebResponse)req.GetResponse();
            responseHeaders = res.Headers;
            if (keepContext)
            {
                context.Cookies = res.Cookies;
                context.Referer = url;
            }
            return res;
        }

        /// <summary>
        /// 發出一次新的請求,並返回回應內容的流
        /// 調用此方法永遠不會觸發StatusUpdate事件.
        /// </summary>
        /// <returns>包含回應主體內容的流</returns>
        public Stream GetStream()
        {
            return GetResponse().GetResponseStream();
        }

        /// <summary>
        /// 發出一次新的請求,並以位元組數組形式返回回應的內容
        /// 調用此方法會觸發StatusUpdate事件
        /// </summary>
        /// <returns>包含回應主體內容的位元組數組</returns>
        public byte[] GetBytes()
        {
            HttpWebResponse res = GetResponse();
            int length = (int)res.ContentLength;

            MemoryStream memoryStream = new MemoryStream();
            byte[] buffer = new byte[0x100];
            Stream rs = res.GetResponseStream();
            for (int i = rs.Read(buffer, 0, buffer.Length); i > 0; i = rs.Read(buffer, 0, buffer.Length))
            {
                memoryStream.Write(buffer, 0, i);
                OnStatusUpdate(new StatusUpdateEventArgs((int)memoryStream.Length, length));
            }
            rs.Close();

            return memoryStream.ToArray();
        }

        /// <summary>
        /// 發出一次新的請求,以Http頭,或Html Meta標簽,或DefaultEncoding指示的編碼信息對回應主體解碼
        /// 調用此方法會觸發StatusUpdate事件
        /// </summary>
        /// <returns>解碼後的字元串</returns>
        public string GetString()
        {
            byte[] data = GetBytes();
            string encodingName = GetEncodingFromHeaders();

            if (encodingName == null)
                encodingName = GetEncodingFromBody(data);

            Encoding encoding;
            if (encodingName == null)
                encoding = defaultEncoding;
            else
            {
                try
                {
                    encoding = Encoding.GetEncoding(encodingName);
                }
                catch (ArgumentException)
                {
                    encoding = defaultEncoding;
                }
            }
            return encoding.GetString(data);
        }

        /// <summary>
        /// 發出一次新的請求,對回應的主體內容以指定的編碼進行解碼
        /// 調用此方法會觸發StatusUpdate事件
        /// </summary>
        /// <param name="encoding">指定的編碼</param>
        /// <returns>解碼後的字元串</returns>
        public string GetString(Encoding encoding)
        {
            byte[] data = GetBytes();
            return encoding.GetString(data);
        }

        private string GetEncodingFromHeaders()
        {
            string encoding = null;
            string contentType = responseHeaders["Content-Type"];
            if (contentType != null)
            {
                int i = contentType.IndexOf("charset=");
                if (i != -1)
                {
                    encoding = contentType.Substring(i + 8);
                }
            }
            return encoding;
        }

        private string GetEncodingFromBody(byte[] data)
        {
            string encodingName = null;
            string dataAsAscii = Encoding.ASCII.GetString(data);
            if (dataAsAscii != null)
            {
                int i = dataAsAscii.IndexOf("charset=");
                if (i != -1)
                {
                    int j = dataAsAscii.IndexOf("\"", i);
                    if (j != -1)
                    {
                        int k = i + 8;
                        encodingName = dataAsAscii.Substring(k, (j - k) + 1);
                        char[] chArray = new char[2] { '>', '"' };
                        encodingName = encodingName.TrimEnd(chArray);
                    }
                }
            }
            return encodingName;
        }

        /// <summary>
        /// 發出一次新的Head請求,獲取資源的長度
        /// 此請求會忽略PostingData, Files, StartPoint, EndPoint, Verb
        /// </summary>
        /// <returns>返回的資源長度</returns>
        public int HeadContentLength()
        {
            Reset();
            HttpVerb lastVerb = verb;
            verb = HttpVerb.HEAD;
            using (HttpWebResponse res = GetResponse())
            {
                verb = lastVerb;
                return (int)res.ContentLength;
            }
        }

        /// <summary>
        /// 發出一次新的請求,把回應的主體內容保存到文件
        /// 調用此方法會觸發StatusUpdate事件
        /// 如果指定的文件存在,它會被覆蓋
        /// </summary>
        /// <param name="fileName">要保存的文件路徑</param>
        public void SaveAsFile(string fileName)
        {
            SaveAsFile(fileName, FileExistsAction.Overwrite);
        }

        /// <summary>
        /// 發出一次新的請求,把回應的主體內容保存到文件
        /// 調用此方法會觸發StatusUpdate事件
        /// </summary>
        /// <param name="fileName">要保存的文件路徑</param>
        /// <param name="existsAction">指定的文件存在時的選項</param>
        /// <returns>是否向目標文件寫入了數據</returns>
        public bool SaveAsFile(string fileName, FileExistsAction existsAction)
        {
            byte[] data = GetBytes();
            switch (existsAction)
            {
                case FileExistsAction.Overwrite:
                    using (BinaryWriter writer = new BinaryWriter(new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Write)))
                        writer.Write(data);
                    return true;

                case FileExistsAction.Append:
                    using (BinaryWriter writer = new BinaryWriter(new FileStream(fileName, FileMode.Append, FileAccess.Write)))
                        writer.Write(data);
                    return true;

                default:
                    if (!File.Exists(fileName))
                    {
                        using (
                            BinaryWriter writer =
                                new BinaryWriter(new FileStream(fileName, FileMode.Create, FileAccess.Write)))
                            writer.Write(data);
                        return true;
                    }
                    else
                    {
                        return false;
                    }
            }
        }
    }

    public class HttpClientContext
    {
        private CookieCollection cookies;
        private string referer;

        public CookieCollection Cookies
        {
            get { return cookies; }
            set { cookies = value; }
        }

        public string Referer
        {
            get { return referer; }
            set { referer = value; }
        }
    }

    public enum HttpVerb
    {
        GET,
        POST,
        HEAD,
    }

    public enum FileExistsAction
    {
        Overwrite,
        Append,
        Cancel,
    }

    public class HttpUploadingFile
    {
        private string fileName;
        private string fieldName;
        private byte[] data;

        public string FileName
        {
            get { return fileName; }
            set { fileName = value; }
        }

        public string FieldName
        {
            get { return fieldName; }
            set { fieldName = value; }
        }

        public byte[] Data
        {
            get { return data; }
            set { data = value; }
        }

        public HttpUploadingFile(string fileName, string fieldName)
        {
            this.fileName = fileName;
            this.fieldName = fieldName;
            using (FileStream stream = new FileStream(fileName, FileMode.Open))
            {
                byte[] inBytes = new byte[stream.Length];
                stream.Read(inBytes, 0, inBytes.Length);
                data = inBytes;
            }
        }

        public HttpUploadingFile(byte[] data, string fileName, string fieldName)
        {
            this.data = data;
            this.fileName = fileName;
            this.fieldName = fieldName;
        }
    }

    public class StatusUpdateEventArgs : EventArgs
    {
        private readonly int bytesGot;
        private readonly int bytesTotal;

        public StatusUpdateEventArgs(int got, int total)
        {
            bytesGot = got;
            bytesTotal = total;
        }

        /// <summary>
        /// 已經下載的位元組數
        /// </summary>
        public int BytesGot
        {
            get { return bytesGot; }
        }

        /// <summary>
        /// 資源的總位元組數
        /// </summary>
        public int BytesTotal
        {
            get { return bytesTotal; }
        }
    }
}
.Net類庫里提供了HttpWebRequest等類,方便我們編程與Web伺服器進行交互. 但是實際使用中我們經常會遇到以下需求,基礎類里沒有直接提供相應的功能(WebClient類包含這些功能,只是用起來稍微麻煩一點--謝謝網友東吳居士的提醒):

  • 對HttpWebResponse獲取的HTML進行文字編碼轉換,使之不會出現亂碼;
  • 自動在Session間保持Cookie,Referer等相關信息;
  • 模擬HTML表單提交;
  • 向伺服器上傳文件;
  • 對二進位的資源,直接獲取返回的位元組數組(byte[]),或者保存為文件

為瞭解決這些問題,我開發了HttpClient類.下麵是使用的方法:

  • 獲取編碼轉換後的字元串

    HttpClient client=new HttpClient(url);
    string html=client.GetString();

    GetString()函數內部會查找Http Headers, 以及HTML的Meta標簽,試圖找出獲取的內容的編碼信息.如果都找不到,它會使用client.DefaultEncoding, 這個屬性預設為utf-8, 也可以手動設置.
  • 自動保持Cookie, Referer

    HttpClient client=new HttpClient(url1, null, true);
    string html1=client.GetString();
    client.Url=url2;
    string html2=client.GetString();

    這裡HttpClient的第三個參數,keepContext設置為真時,HttpClient會自動記錄每次交互時伺服器對Cookies進行的操作,同時會以前一次請求的Url為Referer.在這個例子里,獲取html2時,會把url1作為Referer, 同時會向伺服器傳遞在獲取html1時伺服器設置的Cookies. 當然,你也可以在構造HttpClient時直接提供第一次請求要發出的Cookies與Referer:

    HttpClient client=new HttpClient(url, new WebContext(cookies, referer), true);

    或者,在使用過程中隨時修改這些信息:

    client.Context.Cookies=cookies;
    client.Context.referer=referer;
  • 模擬HTML表單提交

    HttpClient client=new HttpClient(url);
    client.PostingData.Add(fieldName1, filedValue1);
    client.PostingData.Add(fieldName2, fieldValue2);
    string html=client.GetString();

    上面的代碼相當於提交了一個有兩個input的表單. 在PostingData非空,或者附加了要上傳的文件時(請看下麵的上傳和文件), HttpClient會自動把HttpVerb改成POST, 並將相應的信息附加到Request上.
  • 向伺服器上傳文件

    HttpClient client=new HttpClient(url);
    client.AttachFile(fileName, fieldName);
    client.AttachFile(byteArray, fileName, fieldName);
    string html=client.GetString();

    這裡面的fieldName相當於<input type="file" name="fieldName" />里的fieldName. fileName當然就是你想要上傳的文件路徑了. 你也可以直接提供一個byte[] 作為文件內容, 但即使如此,你也必須提供一個文件名,以滿足HTTP規範的要求.
  • 不同的返回形式

    字元串: string html = client.GetString();
    流: Stream stream = client.GetStream();
    位元組數組: byte[] data = client.GetBytes();
    保存到文件:  client.SaveAsFile(fileName);
    或者,你也可以直接操作HttpWebResponse: HttpWebResponse res = client.GetResponse();

    每調用一次上述任何一個方法,都會導致發出一個HTTP Request, 也就是說,你不能同時得到某個Response的兩種返回形式.
    另外,調用後它們任意一個之後,你可以通過client.ResponseHeaders來獲取伺服器返回的HTTP頭.
  • 下載資源的指定部分(用於斷點續傳,多線程下載)

    HttpClient client=new HttpClient(url);
    //發出HEAD請求,獲取資源長度
    int length=client.HeadContentLength();

    //只獲取後一半內容
    client.StartPoint=length/2;
    byte[] data=client.GetBytes();

    HeadContentLength()只會發出HTTP HEAD請求.根據HTTP協議, HEAD與GET的作用等同, 但是,只返回HTTP頭,而不返回資源主體內容. 也就是說,用這個方法,你沒法獲取一個需要通過POST才能得到的資源的長度,如果你確實有這樣的需求,建議你可以通過GetResponse(),然後從ResponseHeader里獲取Content-Length.

計劃中還有另外一些功能要加進來,比如斷點續傳, 多線程下載, 下載進度更新的事件機制等, 正在思考如何與現在的代碼融合到一起,期待你的反饋.

 

註意:使用時應該添加對System.Web.dll的引用,併在使用此類的代碼前添加"using System.Web;",不然會無法通過編譯.

文章來源:http://www.cnblogs.com/elliotta/archive/2009/02/11/1388484.html


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

-Advertisement-
Play Games
更多相關文章
  • 表格能夠直觀的傳達數據信息,使信息顯得條理化,便於閱讀同時也利於管理。那在PDF類型的文檔中如何來添加表格並且對錶格進行格式化操作呢?使用常規方法直接在PDF中添加表格行不通,那我們可以在藉助第三方組件的情況下來實現。本篇文章中將介紹如何正確使用組件Free Spire.PDF for .NET添加 ...
  • 在寫程式的過程中經常遇到將一個對象中的屬性值賦給另一個對象,一般情況下我們都是一個一個屬性對應賦值,但是這樣過於繁雜,並且當類的屬性發生變化時,還要去修改對應關係。基於這種需求,我寫了一個幫助類,方便大家複製屬性值,有什麼不足之處或者需要改進的地方希望大家指出,如有更好的方法還請賜教。 ...
  • 獲取2個集合List<T>的共同元素,迴圈2個集合,然後比對。 class Bj { public void GetIntersect() { var listA = new List<int> { 2, 5, 6, 8, 23, 56, 4 }; var listB = new List<int> ...
  • yield是對一種複雜行為的簡化,就是將一段代碼簡化為一種簡單的形式。 先看一下常規的寫法,下麵例子中,把找出字元串陣列中,某些元素包含有某些字元的元素。 class Bi { public string[] str { get; set; } public IEnumerable<string> ...
  • 第一次接觸ABP框架。以下是一些學習過程中的總結,如有不正確的地方,歡迎指導!!!感謝感謝! 因為原來用的是vs2015,所以剛碰ABP框架的時候連官網下載的模板都運行不成功。各種百度以及詢問以後,才發現這個框架開發需要vs2017!! 需要VS2017!需要vs2017!!需要VS2017!!! ...
  • NanUI基於ChromiumFX項目進行開發,它能讓你在你的Winform應用程式中使用HTML5/CSS3/Javascript等網頁技術來呈現用戶界面(類似Electron)。同時NanUI提供了原生視窗和定製化的無標題欄無邊框視窗,你能使用全部的網頁技術來設計和呈現你的應用程式界面。 ...
  • NanUI文檔目錄 "NanUI簡介" "開始使用NanUI" "打包並使用內嵌式的HTML/CSS/JS資源" "使用網頁來設計整個視窗" 如何實現C 與Javascript相互掉用 (待更新。。。) 如何處理NanUI中的下載過程 DonwloadHandler的使用 (待更新。。。) 如何處理 ...
  • 微信掃一掃並支付成功,聯繫QQ:770628656獲取所有源碼(超級代碼詳細註釋版本) ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...