用ASP.NET Core 2.1 建立規範的 REST API -- 翻頁/排序/過濾等

来源:https://www.cnblogs.com/cgzl/archive/2018/06/07/9117448.html
-Advertisement-
Play Games

本文主要介紹一些常見情況的實現,包括:集合更新、翻頁、排序、過濾等等。但是仍然是Richardson成熟度為2級的Web API,未達到RESTful API的標準。 本代碼已經更新至ASP.NET Core 2.1. ...


本文所需的一些預備知識可以看這裡: http://www.cnblogs.com/cgzl/p/9010978.html 和 http://www.cnblogs.com/cgzl/p/9019314.html

建立Richardson成熟度2級的POST、GET、PUT、PATCH、DELETE的RESTful API請看這裡:https://www.cnblogs.com/cgzl/p/9047626.html 和 https://www.cnblogs.com/cgzl/p/9080960.html

本文需要的代碼 (右鍵另存,把尾碼改為zip):https://images2018.cnblogs.com/blog/986268/201806/986268-20180604151009219-514390264.jpg

本代碼已經更新至ASP.NET Core 2.1. (從ASP.NET Core 2.0 遷移至 ASP.NET Core 2.1: https://docs.microsoft.com/en-us/aspnet/core/migration/20_21?view=aspnetcore-2.1)

 

本文主要介紹一些常見情況的實現,包括:集合更新、翻頁、排序、過濾等等。但是仍然是Richardson成熟度頂多為2級的Web API,未達到RESTful API的標準和約束。

 

集合的更新操作

 

看這種更新集合的情況,原來資料庫里中國存了4個城市(北平,上海,盛京,海參崴);而幾個世紀後北平改名叫北京了,盛京名為沈陽了,海參崴不屬於中國了就刪除了,威海從縣成為市就算是新增,而上海保持不變。現在就是要對中國的城市進行整體性的更新操作,裡面會包含:添加、刪除、更新操作。看代碼:

集合更新,我一共分了三步進行的操作:

1. 把資料庫中存在的但是傳進來的數據里沒有的城市刪掉

2. 把資料庫中沒有的而傳進來的數據里有的數據進行添加操作,其實這裡只判斷id為0即可

3. 把資料庫中原有和傳進來的參數里也存在的數據條目進行更新。

然後保存即可。

先看一下原有的數據:

然後我們執行集合的更新:

執行之後,再次查詢:

集合按預期更新了。

我相信大家肯定會寫這段代碼,或者有更簡單的實現方式(請貼出來)。但這不是重點,我看到有人這樣寫,把上面那三步代碼寫在了AutoMapper的配置文件里:

首先,需要忽略Country的Cities屬性的映射操作,然後把那部分代碼寫在AfterMap裡面即可,這樣在Action方法裡面就簡單了,可以使用Automapper了:

這隻是一種可選的寫法而已,不一定就必須放在AutoMapper的配置文件里。

 

翻頁

翻頁可以避免一些性能問題,不必一次性載入所有數據。所以最好預設就採用分頁,而且每頁的條目數量必須有限制,不能太大。

分頁信息應該使用查詢字元串(query stringg)傳遞參數。格式應該這樣:

http://localhost:5000/api/country?pageIndex=12&pageSize=10

這裡我喜歡使用pageIndex這個詞,這也意味著頁數是從0開始的;當然很多人喜歡用pageNumber等詞,也就是說更喜歡頁數從1開始,這個其實隨意吧。

在ASP.NET Core里,我要使用Linq來動態組建一個查詢的表達式(IQueryable<T>,可以創建表達式樹),它是延遲執行的,直到各種條件都判斷完了並組建出最終的查詢表達式之後才去執行(查詢資料庫)。這個查詢表達式只有在進行迭代的時候才會查詢資料庫。

觸發迭代動作可以使用下麵的方法:

  • foreach 迴圈
  • ToList(), ToArray(), ToDictionary() 以及相應的非同步版本(ToXxxxAsync())
  • 單項查詢,例如 Average(), Count(), First(), FirstOrDefault(), SingleOrDefault()等等,以及相應的非同步版本。

需要確保的是要在迭代發生之前,使用Skip()和Take()以及Where()。

下麵我一點一點來寫代碼:

首先我們需要從參數(query string參數)傳進來pageIndex和pageSize,還要賦預設值,以防止API的消費者沒有設置pageIndex和pageSize;由於pageSize的值是由API的消費者來定的,所以應該在後端設定一個最大值,以免API的消費者設定一個很大的值。

由於所有的資源幾乎都要使用翻頁,所以我們最好使用一個公共類來封裝這些翻頁相關的信息:

(我暫時把這個類放在了Core項目里)

這個公共類很簡單,可以為pageIndex和pageSize設定預設值,也設置了一個每頁的最多條目數是100;這裡面還有一個OrderBy屬性,預設值是“Id”,因為翻頁必須要先排序,但目前這個OrderBy屬性還沒用上。

而針對具體的資源,我們可以再建立一個類繼承於PaginationBase,這個類就是Country的參數類:

由於暫時還沒有什麼特別的參數,所以裡面是空的。

下麵我修改一下CountryRepository:

可以看到我組建了這個查詢的表達式,並且直接出發了迭代動作,返回查詢結果。

回到Action方法里:

我使用了這個參數類代替了之前的pageIndex和pageSize參數,因為ASP.NET Core足夠智能,可以把這兩個參數解析到這個類裡面。

下麵測試一下:

我就不進行多次測試了,這個是好用的。

如果你是用的是關係型資料庫的話,應該可以在Log的輸出媒介上看到列印出的SQL語句(但我這裡使用的是記憶體資料庫,所以看不到),如果使用關係型資料庫還是看不到SQL語句的話,請配置一下:

返回翻頁的元數據

很顯然只返回當前頁的數據是不滿足需求的,至少還需要返回總頁數,總數等信息,還有可能需要返回前一頁或者後一頁的鏈接。但是如何把這些信息連同當頁的數據一起返回給API消費者呢?

下麵的做法是可以把這些數據都返回去的:

{
     “data”: [{country1}, {country2}...],
     “metadata”: {"prev": "/api/...", ....}    
}

但是這樣做的話就導致了響應的body不再符合Accept Header了(不是資源的JSON表述了),也就不是application/json了,而是一種新的media type。

所以如果返回這樣的數據就違反了REST的規則了(儘管本文代碼的Richardson成熟度最多也就是2級),它違反了自我描述的約束(請參考本系列的預備知識文章),API消費者不知道如何通過application/json這個設定的contety-type來解釋響應數據了。

所以說翻頁的元數據並不是資源表述的一部分。我們應該使用自定義的Header,例如“X-Pagination”來表述翻頁元數據,這個名也是比較常用的。

首先,我創建一個類可以存放翻頁的數據:

可以向上面這樣做這個類:該類繼承於List<T>,同時還包含PaginationBase作為屬性,還可以判斷是否有前一頁和後一頁。使用靜態方法創建該類的實例。

這個靜態方法也許會有一點點問題,這裡沒有使用非同步方法,這樣做是OK的;但是如果使用非同步方法,例如source.CountAsync()和source.ToListAsync(),就會有一些問題,因為我需要修改CountryRepository的GetCountriesAsync方法的返回類型,改成上面這個類型,所以它的介面ICountryRepository也需要改;而它的介面是整個項目的核心並放在Core項目里,而整個項目的核心(合約)我個人認為應該是和具體的ORM無關的,但是這裡依賴於EntityFrameworkCore了(ToListAsync())。所以我最後決定去掉這個靜態方法,這樣可能會導致多寫一些代碼;此外還添加HasPrevious和HasNext屬性,判斷是否有前一頁和後一頁:

(暫時放在Core項目裡面了)

然後修改CountryRepository:

然後在Action方法里,我們還需要生成前一頁和後一頁的URI,所以這裡可以使用UrlHelper,需要在Startup的ConfigureServices方法裡面註冊:

然後回到Controller裡面建立一個方法來生成URI:

在這裡我還建立了一個枚舉,PaginationResourceUriType。我還為PaginationBase添加了一個Clone()方法,目的是創建出一個屬性值和它相同的另一個實例,因為這裡有修改pageIndex屬性這個操作;也許Clone不是最好的辦法,直接new可能更合適。

下麵就是修改Action方法了:

通過之前的方法分別創建出兩個鏈接,然後把翻頁相關的數據組成一個匿名類,使用JSON.NET將其串列化,並放到響應的自定義Header:“X-Pagination”裡面。

而body部分還是資源的集合數據。

測試一下:

響應的body正常的返回來了,再看一下響應的Header:

可以看到自定義的X-Pagination Header了,然後我複製一下裡面的NextPageLink鏈接,併發送該請求:

都沒有問題。

這個Action目前的Richardson成熟度已經接近3級了(HATEOAS),但還不是。翻頁現在是到這,下麵要進行過濾並翻頁。

 

過濾和搜索

過濾的意思就是對集合資源附加一些條件然後篩選出結果,它的URI是下麵的形式:

http://localhost:5000/api/countries?englishName=China

所以需要在查詢字元串里寫上屬性的名字和屬性的值來表示要按這個屬性的值來進行過濾,當然也可以寫多個過濾的條件。

過濾的條件是應用於ResourceModel(或叫做Dto,ViewModel),例如CountryResource,而不應用於其它級別的Model,因為API消費者只知道ResourceModel,它不知道內部實現的細節,也就是不知道EntityModel的樣子。

 

搜索呢,是通過一個搜索關鍵字來模糊的篩選集合資源,可能會有多個屬性針對這個關鍵字進行模糊篩選。

搜索的URI大致是下麵的形式:

http://localhost/api/countries?searchTerm=hin

 

上面這個URI可以理解為針對Countries資源,凡是字元串類型的屬性,它的值包含hin的都符合條件,就返回符合這個條件的結果。

首先看一下過濾的實現。在Countries的GET Action方法里,我使用CountryResourceParameters類作為參數,所以要增加針對某個屬性的過濾條件,只需擴展這個類即可,而增加的屬性名要和ResourceModel裡面的屬性名一致:

然後是修改CountryRepository裡面的方法:

首先要在執行分頁動作之前附加過濾條件,query的類型必須是IQueryable<Country>才可以動態組建查詢表達式,所以使用了AsQueryable()方法;然後分別判斷兩個條件並附加條件(註意大小寫問題和兩頭空格的問題),最後再執行分頁查詢。

由於添加了參數,所以CreateUri的方法也需要改:

這個方法參數變成了CountryResourceParameters,而且Clone方法克隆出來的也是CountryResourceParameters類:

下麵測試:

沒有問題的,但是還要看看Header:

針對這個結果是OK的。

下麵我做一些數據,使其擁有同樣的EnglishName,然後測試:

 

 OK,再看看Header:

使用NextLink再次發送請求, 結果是OK的,我就不貼圖了。

但是你應該註意到,X-Pagination的屬性名不符合camelCase命名規範,所以需要在轉化成JSON的時候添加一些配置:

然後再測試一下:

屬性的命名符合camelcase規範了,但是previousLink和nextLink裡面的查詢字元串的大小寫依然不正確,所以我乾脆去掉了Clone()方法,然後在CreateCountryUri的方法里直接new出來新鏈接的參數:

測試:

現在命名終於符合規範了。

 

排序

之前做的翻頁都需要排序,暫時都是按照Id進行排序的。而實際上API消費者可能讓資源按照資源的某個屬性或多個屬性進行正向或反向的排序

我們先從最簡單的例子開始,只考慮只按照某一個屬性(針對的是資源的屬性,例如CountryResource的EnglishName)進行排序,針對這個例子,我先使用比較笨的方法。

首先我假定,參數類裡面的OrderBy屬性如果以" desc" 結尾,例如:“EnglishName desc”,那麼就是按照EnglishName倒序排列,而“EnglishName”就是正序排列。

只需在CountryRepository裡面修改代碼即可:

 

嗯,很笨重的代碼。

先測試一下:

至少功能是OK的,再看一下倒序:

也OK,所以雖然代碼很笨重,但是針對這種簡單的情況是可以應付的。

下麵我們對它進行第一次優化。像上面這樣挨個屬性的判斷實在是太費勁了,所以我們來分析一下,OrderBy的值是字元串,而OrderBy()方法裡面的lambda表達式的類型是Expression具體的類型是Expression<Func<Country, object>>。這裡簡單講一下,萬一您不知道lambda表達式的話可以看一下。lambda表達式就是匿名的函數,它的類型是Func(可以賦值給Func類型的變數):

同時我們也可以把這個lambda表達式賦值給Expression:

而OrderBy()這個Linq方法接收的參數類型就是Expression<Func<Country, object>>。

使用Expression,我們可以構建Expression Tree;使用Expression Tree,可以表示一些邏輯。而在運行時,Linq的提供商將會解析這個Expression Tree,並把這些邏輯轉化為SQL語句:

再看上面的排序條件判斷,我們可以把OrderBy的字元串和Expression映射起來,就像Key-Value 鍵值對那樣,這樣做也許就會是代碼稍微好看一些。所以你肯定會想到Dictionary<K, V>。

所以修改後的代碼如下:

我相信你能看懂,我就不解釋了,下麵測試:

總之是好用的,我就不貼其他測試結果的圖片了。

應該把上面這段代碼提取出來封裝成一個方法函數並泛型化,但是我暫時先不這樣做。

 

經過第一次優化,使用Dictionary,代碼簡潔了許多,但是期間還是有手動把屬性名字元串轉化為Expression的動作。之所以這麼寫是因為OrderBy僅支持Expression的參數類型,如果支持字元串,那就完美了。

幸好有一個微軟的庫支持這種操作,它叫做System.Linq.Dynamic.Core(其作者是紅衣教主啊)

我把它安裝在了Infrastructure項目里供Repository使用。

再次修改排序那部分的代碼:

註意這裡OrderBy的命名空間是:System.Linq.Dynamic.Core

經過第二次優化,代碼已經很簡潔了,但是還有很多待完善的地方,例如:

  • Resource Model的一個屬性可能會映射到Entity Model的多個屬性上:Name 屬性通常會映射成EntityModel的 FirstName 和 LastName屬性
  • Resource Model上的正序可能在Entity Model上就是倒序的:Age 升序,而Entity Model的BirthDate就是降序
  • 需要支持多屬性的排序:EnglishName desc, Id, ChineseName。
  • 復用

 

第三次優化,要解決Model屬性映射引起的問題。

也就是說要從ResourceModel的一個屬性映射到Entity Model的一個或者多個屬性上,而且它們之間的排列順序可能是不同的,舉一個極端的例子:

假設ResourceModel 有個屬性叫做 Rank(排名) ,它所映射Entity Model的兩個屬性Result(成績)和Weight(體重);假設這是舉重比賽的Model,排名結果(Rank)是按照成績(Result)從高到低排序的,但是如果多名選手的成績相同,則體重輕的排名靠前。

也就是Rank asc -> Result desc, Weight asc。

用程式來說就是,一個字元串“Rank asc”要映射成一個集合,而集合元素的類型有兩個屬性:Entity Model的屬性名和排序的方向。

所以先把集合里這種元素的類建立出來:

這裡方向我是用的Revert這個單詞,表示其方向是否與Resource Model的屬性方向相反即可。

然後在做針對CountryResource的整套映射,不過首先我考慮建立一個抽象父類,裡面可能有些公用的東西:

由於Id這個屬性可能是每個相關的Model共有的,所以在這個父類里,我添加了Id屬性的映射,Id是一對一的映射,排序方向相同。

然後我針對CountryResource,寫一個派生於PropertyMapping的子類:

註意紅框很重要,比較key的時候忽略大小寫。

到這裡,Resource和Entity Model之間映射的部分差不多做完了,接下來要考慮整個排序的問題,做這樣一個擴展方法:

它應用於IQueryable,並把orderBy字元串和屬性映射表傳進來。

經過一些初步檢驗之後,把orderBy按“,”分解成欄位屬性的數組。然後去掉兩邊可能存在的空格,判斷是否是倒序,提取出屬性的名稱。如果在映射表裡面找不到該名稱或者該名稱對應的值是空,那就拋出異常。

然後先迴圈欄位數組,然後內層迴圈該欄位映射的屬性集合。

最後通過DynamicLinq即可組建出所需的排序表達式。

使用DynamicLinq的OrderBy時要註意,排序條件必須反向附加,不信可以試試。

隨後我們修改一下Repository:

就剩下一句話了,很簡潔了。但是這裡需要new一個CountryPropertyMapping類,這樣做對單元測試就不友好了,也許把它放在一個容器里取出來用更合適?

那麼就建立一個容器:

該容器的Register和Resolve分別用來註冊和提取映射表。

下麵還有個檢查映射是否存在的方法,fields是一個或者多個欄位屬性組成的字元串,其格式如“EnglishName,ChineseName”;它檢查是否能在映射配置表(MappingDictionary)找到相應的Key,如果找不到就驗證失敗。

這個容器在整個應用範圍內也是個容器,所以需要在Startup裡面註冊,由於它的代碼可能比較多(因為本身它也是個容器,還有很多註冊內容用的代碼),所以我單獨寫了個擴展方法:

該方法可以在Startup裡面調用,從而註冊到ASP.NET Core的服務容器里:

然後再次修改CountryRepository:

先註入了該容器服務,然後從該容器中按照映射兩端的Model類型取出需要的映射表:

 測試:

看起來是OK的,那我們針對排序,暫時先優化到這裡。

 

排序的異常

還需要考慮到如果OrderBy裡面的欄位在映射表裡面不存在的情況,所以我使用這個方法來進行判斷:

我把這個方法放在了PropertyMappingContainer里,因為PropertyMappingContainer本身實際上就是一個服務,放在里還是比較合適的。

這裡需要註意的是fileds裡面的欄位可能是這種形式的“EnglishName desc”,所以需要把空格和desc部分去掉。

隨後在Action方法里調用即可:

測試:

應該是沒問題的,我就不多測試了,以後要實行單元測試的。

 

資源塑形

如果某個資源的屬性比較多,那麼客戶端的API消費者可能只需要一部分屬性,這時就應該進行數據塑形,而且這樣做有可能會提升性能。

數據塑形要考慮兩種情況,集合資源單個資源

集合資源塑形

先考慮集合資源,首先我做一個擴展方法,把IEnumerable<T>可以轉化為IEnumerable<dynamic>,這裡要用到dynamic(ExpandoObject):

由於反射比較消耗資源,所以在這裡,我一次性把需要的屬性弄成PropertyInfo放到了一個集合里。如果fields是空的,說明需要所有屬性,就把所有public和實例的property都放到集合里,否則,就把需要的屬性放進去即可。

然後迴圈數據源,使用反射通過PropertyInfo獲取該屬性的值,最後組成一個ExpandoObject,再把這個ExpandoObject放到結果集合裡面即可。

接下來修改參數類,因為這是個通用的東西,那就是為PaginationBase添加一個Fields屬性吧:

最後修改Action方法:

測試:

好用的。但是返回的數據並不是camelcase的,這是因為JSON.net串列化的ContractResolver並不適用於Dictionary。下麵來處理這個問題。

打開Startup,在services.AddMvc()後邊添加:

這句話就是配置了JSON轉化的ContractResolver。

在測試一下:

現在Ok了。

處理異常

但如果API消費者在Fields裡面提供了不存在的屬性,那麼就應該返回Bad Request。

原理上我也許可以使用ProperyMappingContainer裡面的驗證方法,但是數據塑形並不使用映射表。而且目的不同,一個是排序一個是數據塑形,所以因為關註分離吧(SoC)。

我們要做的就是給定一個Fields和一個類型,需要判斷Fields裡面包含的欄位屬性在這個類型裡面都存在,所以還是做一個Service比較好,可以註入使用。

看代碼:

這個類比較簡單不多講了,別忘了在Startup裡面註冊。

然後在Controller裡面註入並使用,別忘了還需要修改CreateCountryUri方法:

測試:

OK.

對單個資源塑形

這個跟集合的原理差不多,先建立一個擴展方法:

再修改Action即可:

測試:

 

是好用的,我就不多測試了。

 

針對數據塑形需要註意的是,儘量把Id帶上,否則可能無法獲取相關的鏈接了。

 

今天先寫到這裡,還有很多更深入一點的功能沒有做,我就不做了。

到目前為止,這些Web API仍然稱不上是RESTful的API,成熟度不夠高,有些約束也沒達到。下一篇文章會把升級這些API以便支持HATEOAS。

代碼在這:https://github.com/solenovex/ASP.NET-Core-2.0-RESTful-API-Tutorial

項目有一些文件的拜訪目錄可能不對,暫時先不處理。

 


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

-Advertisement-
Play Games
更多相關文章
  • 最近有空下來先停下腳步,全面整理下框架相關主題的內容,包括框架模塊內容的介紹,DevExpress界面開發,框架快速開發等主題的內容,一個目的是分享給讀者,還有一個重要的目的也是全面整理下自己這十幾年的成果和開發心得,本篇主要是針對公用類庫模塊做一些內容的介紹,整個類庫其實涉及麵包括原生.NET公用... ...
  • 值類型 和引用類型的介紹 直接上代碼看: public class Study { public static int initNo = 100; public static void Test1(int i) { i = 1; } public static void Test1(ref int ...
  • 1.首先建立好XML 。可以通選自定義EXCEL導出XML格式的數據:(如圖) 2 讀取XML 文件 具體的詳細講解 可以查看 改網址 :https://blog.csdn.net/dyllove98/article/details/8708323#C5 ...
  • 1 <DataGrid Height="Auto" Width="Auto"> 2 <DataGrid.Columns> 3 <DataGridTextColumn Binding="{Binding ItemName}" Header="Name" Width="2*" /> 4 <DataGri ...
  • Swagger也算是行之有年的API文件生成器,只要在API上使用C#的<summary />文件註解標簽,就可以產生精美的線上文件,並且對RESTful API有良好的支持。不僅支持生成文件,還支持模擬調用的交互功能,連Postman都不用打開就能測API。本篇將介紹如何通過Swagger產生AS ...
  • 模型原型:伺服器的配置和運行狀態信息。 設計要求:Json格式數據解析後,判斷配置信息是否是新數據或者是否更新。如是新數據,則直接添加到資料庫;若是數據更新,則更新資料庫配置信息並更新運行狀態信息;都不是則僅將運行狀態添加到資料庫。最後從資料庫取數據並展示。 模型難點:每個伺服器會搭載多個網卡和最多 ...
  • innerHTML與innerText區別 setInterval和setTimeout的使用區別 setInterval在執行完一次代碼之後,經過了那個固定的時間間隔,它還會自動重覆執行代碼,而setTimeout只執行一次那段代碼。 MVC有幾種向頁面綁定數據的方法 1、指定頁面數據的強類型Mo ...
  • 一、什麼是API網關 API網關是一個伺服器,是系統對外的唯一入口。API網關封裝了系統內部架構,為每個客戶端提供一個定製的API。API網關方式的核心要點是,所有的客戶端和消費端都通過統一的網關接入微服務,在網關層處理所有的非業務功能。通常,網關也是提供REST/HTTP的訪問API。服務端通過A ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...