ASP.NET Core 2.2 : 十七.Action的執行(Endpoint.RequestDelegate後面的故事)

来源:https://www.cnblogs.com/FlyLolo/archive/2019/01/25/ASPNETCore2_17.html
-Advertisement-
Play Games

上一章介紹了經過路由的處理,一個請求找到了具體處理這個請求的EndPoint,並最終執行它的RequestDelegate方法來處理這個Httpcontext。本章繼續這個處理進程,按照慣例,依然通過幾幅圖來聊一聊這個RequestDelegate之後的故事。在此就避免不了的聊到各種Filter,它 ...


          上一章介紹了經過路由的處理,一個請求找到了具體處理這個請求的EndPoint,並最終執行它的RequestDelegate方法來處理這個Httpcontext。本章繼續這個處理進程,按照慣例,依然通過幾幅圖來聊一聊這個RequestDelegate之後的故事。在此就避免不了的聊到各種Filter,它方便我們在action執行的前後做一些 “小動作”。(ASP.NET Core 系列目錄

一、概述

          首先看一下RequestDelegate這個方法:

RequestDelegate requestDelegate = (context) =>
{
    var routeData = context.GetRouteData();
    var actionContext = new ActionContext(context, routeData, action);
    var invoker = _invokerFactory.CreateInvoker(actionContext);

    return invoker.InvokeAsync();
};

          將這個方法的內容分為兩部分:

          A. invoker的生成,前三句,通過CreateInvoker方法生成了一個invoker,它是一個比較複雜的綜合體,包含了controller的創建工廠、參數的綁定方法以及本action相關的各種Filter的集合等, 也就是說它是前期準備工作階段,這個階段擴展開來涉及面比較廣,在下文詳細描述。  

          B.invoker的執行,最後一句,前面已經萬事俱備,準備了好多方法,到此來執行。此時涉及到前面準備的各種方法的執行,各種Filter也在此時被執行。

二、invoker的生成

           依照習慣,還是通過流程圖來看一看:

                                                                                                     圖一(點擊查看大圖

          首先說一下此圖的結構,每個泳道相當於是上一個泳道中的圖標的細化說明,例如第二條泳道是圖標標識的方塊的明細化。

          A. 泳道一:

                  就是第一節【概述】中描述的的內容,不再贅述。另外提一下本文的核心invoker本質上就是一個ControllerActionInvoker,也是圖中的ActionInvokerProviderContext.Result。

          B.泳道二:即①的詳細描述

          ① ActionInvokerFactory.CreateInvoker(actionContext)

 1 public IActionInvoker CreateInvoker(ActionContext actionContext)
 2 {
 3     var context = new ActionInvokerProviderContext(actionContext);
 4 
 5     foreach (var provider in _actionInvokerProviders)
 6     {
 7         provider.OnProvidersExecuting(context);
 8     }
 9 
10     for (var i = _actionInvokerProviders.Length - 1; i >= 0; i--)
11     {
12         _actionInvokerProviders[i].OnProvidersExecuted(context);
13     }
14 
15     return context.Result;
16 }

          本章設計的這部分內容比較常見的一個操作就是context的封裝,這從第一個泳道的第一個操作就開始了, 他將HttpContext、RouteData,ActionDescriptor封裝到一起成了一個ActionContext ,而到了這個方法,又將這個ActionContext 封裝成了ActionInvokerProviderContext,接下來就是遍歷_actionInvokerProviders調用它們的OnProvidersExecuting和OnProvidersExecuted方法來設置ActionInvokerProviderContext.Result,也就是最終的ControllerActionInvoker。

          這裡說一下_actionInvokerProviders,它的類型是IActionInvokerProvider[],預設情況下包含了兩個,分別是ControllerActionInvokerProvider和PageActionInvokerProvider,第一個是用於MVC的action的處理,而第二個用於Razor Pages Web的處理。二者的OnProvidersExecuting方法都會首先判斷當前action是不是自己對應的類型,若不是則直接跳過。二者的OnProvidersExecuted方法目前均為空。所以圖中和下麵關於OnProvidersExecuting的描述也僅限於ControllerActionInvokerProvider的OnProvidersExecuting方法。

          C.泳道三:ControllerActionInvokerProvider.OnProvidersExecuting(context)

          即泳道二中的③的詳細描述

 1 public void OnProvidersExecuting(ActionInvokerProviderContext context)
 2 {
 3     if (context.ActionContext.ActionDescriptor is ControllerActionDescriptor)
 4     {
 5         var controllerContext = new ControllerContext(context.ActionContext);
 6         // PERF: These are rarely going to be changed, so let's go copy-on-write.
 7         controllerContext.ValueProviderFactories = new CopyOnWriteList<IValueProviderFactory>(_valueProviderFactories);
 8         controllerContext.ModelState.MaxAllowedErrors = _maxModelValidationErrors;
 9 
10         var cacheResult = _controllerActionInvokerCache.GetCachedResult(controllerContext);
11 
12         var invoker = new ControllerActionInvoker(
13             _logger,
14             _diagnosticSource,
15             _mapper,
16             controllerContext,
17             cacheResult.cacheEntry,
18             cacheResult.filters);
19 
20         context.Result = invoker;
21     }
22 }

         如上文所述,在處理之前,首先就是判斷當前action是否是自己對應處理的類型。然後就是繼續封裝大法,將ActionContext封裝成了ControllerContext。進而是調用GetCachedResult方法讀取兩個關鍵內容cacheResult.cacheEntry和cacheResult.filters後,將其封裝成ControllerActionInvoker(⑤)。

          D.第四條泳道:

          對應的是第三條中的④ControllerActionInvokerCache.GetCachedResult(controllerContext);

 1 public (ControllerActionInvokerCacheEntry cacheEntry, IFilterMetadata[] filters) GetCachedResult(ControllerContext controllerContext)
 2 {
 3     var cache = CurrentCache;
 4     var actionDescriptor = controllerContext.ActionDescriptor;
 5 
 6     IFilterMetadata[] filters;
 7     if (!cache.Entries.TryGetValue(actionDescriptor, out var cacheEntry))
 8     {
 9         var filterFactoryResult = FilterFactory.GetAllFilters(_filterProviders, controllerContext);
10         filters = filterFactoryResult.Filters;
11 
12         var parameterDefaultValues = ParameterDefaultValues
13             .GetParameterDefaultValues(actionDescriptor.MethodInfo);
14 
15         var objectMethodExecutor = ObjectMethodExecutor.Create(
16             actionDescriptor.MethodInfo,
17             actionDescriptor.ControllerTypeInfo,
18             parameterDefaultValues);
19 
20         var controllerFactory = _controllerFactoryProvider.CreateControllerFactory(actionDescriptor);
21         var controllerReleaser = _controllerFactoryProvider.CreateControllerReleaser(actionDescriptor);
22         var propertyBinderFactory = ControllerBinderDelegateProvider.CreateBinderDelegate(
23             _parameterBinder,
24             _modelBinderFactory,
25             _modelMetadataProvider,
26             actionDescriptor,
27             _mvcOptions);
28 
29         var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
30 
31         cacheEntry = new ControllerActionInvokerCacheEntry(
32             filterFactoryResult.CacheableFilters,
33             controllerFactory,
34             controllerReleaser,
35             propertyBinderFactory,
36             objectMethodExecutor,
37             actionMethodExecutor);
38         cacheEntry = cache.Entries.GetOrAdd(actionDescriptor, cacheEntry);
39     }
40     else
41     {
42         // Filter instances from statically defined filter descriptors + from filter providers
43         filters = FilterFactory.CreateUncachedFilters(_filterProviders, controllerContext, cacheEntry.CachedFilters);
44     }
45 
46     return (cacheEntry, filters);

           總的來看,本段內容主要是為了組裝cacheEntry和 filters兩個內容,而一個大的 if 體現出這裡加入了緩存機制,使系統不必每次都去拼湊這些,提高執行效率。

⑥IFilterMetadata[] filters,它是一個filter的集和,首先調用FilterFactory的GetAllFilters(_filterProviders, controllerContext)方法獲取當前action對應的所有Filter並對這些Filter進行排序(Filter部分將在之後章節分享)。

接下來就是組裝⑦cacheEntry,它的內容比較多,比較重要的幾個有:⑧ controllerFactory和controllerReleaser他們的本質都是Func<ControllerContext, object>,也就是Controller的Create和Release方法。 ⑨propertyBinderFactory 是一個用於參數綁定的Task,可以說也是一個組裝好準備被執行的方法。最後一個⑩actionMethodExecutor也就是執行者,通過ActionMethodExecutor.GetExecutor(objectMethodExecutor)方法從眾多的action執行者(如圖二)中找出一個當前action對應的執行者出來。

                                                             圖二

總結: 本節invoker的生成,總的來說就是一個執行前“萬事俱備”的過程,invoker是一個組裝起來的集合,它包含一個人(執行者actionMethodExecutor)、N把槍(組裝好用於“被執行”的方法例如controllerFactory、controllerReleaser和propertyBinderFactory,當然還有個filter的集和)。由此也可以進一步想到,接下來的過程就是這些準備好的內容按照一定的順序逐步執行的過程。

 二、invoker的執行

invoker的執行也就是invoker.InvokeAsync(),雖然invoker本質上是ControllerActionInvoker,但這個方法寫在ResourceInvoker類中, ControllerActionInvoker : ResourceInvoker, IActionInvoker 。

public virtual async Task InvokeAsync()
{
    try
    {
        await InvokeFilterPipelineAsync();
    }
    finally
    {
        ReleaseResources();
        _logger.ExecutedAction(_actionContext.ActionDescriptor, stopwatch.GetElapsedTime());
    }
}

private async Task InvokeFilterPipelineAsync()
{
    var next = State.InvokeBegin;
    var scope = Scope.Invoker;
    var state = (object)null;

    // `isCompleted` will be set to true when we've reached a terminal state.
    var isCompleted = false;

    while (!isCompleted)
    {
        await Next(ref next, ref scope, ref state, ref isCompleted);
    }
}

看似比較簡單的兩個方法,從InvokeAsync方法中可以看出來,請求會進入篩選器管道進行處理,也就是 Task InvokeFilterPipelineAsync() 方法,借用官方文檔中的一個圖看一下

                                  圖三

此圖描述了請求經過其他中間件處理後,進入路由處理最終找到了對應的action,最終進入篩選器管道進行處理。而這個處理的核心部分就是方法中的 while (!isCompleted) 迴圈,它對應的Next方法比較長,如下(較長已摺疊)

  1         private Task Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
  2         {
  3             switch (next)
  4             {
  5                 case State.InvokeBegin:
  6                     {
  7                         goto case State.AuthorizationBegin;
  8                     }
  9 
 10                 case State.AuthorizationBegin:
 11                     {
 12                         _cursor.Reset();
 13                         goto case State.AuthorizationNext;
 14                     }
 15 
 16                 case State.AuthorizationNext:
 17                     {
 18                         var current = _cursor.GetNextFilter<IAuthorizationFilter, IAsyncAuthorizationFilter>();
 19                         if (current.FilterAsync != null)
 20                         {
 21                             if (_authorizationContext == null)
 22                             {
 23                                 _authorizationContext = new AuthorizationFilterContext(_actionContext, _filters);
 24                             }
 25 
 26                             state = current.FilterAsync;
 27                             goto case State.AuthorizationAsyncBegin;
 28                         }
 29                         else if (current.Filter != null)
 30                         {
 31                             if (_authorizationContext == null)
 32                             {
 33                                 _authorizationContext = new AuthorizationFilterContext(_actionContext, _filters);
 34                             }
 35 
 36                             state = current.Filter;
 37                             goto case State.AuthorizationSync;
 38                         }
 39                         else
 40                         {
 41                             goto case State.AuthorizationEnd;
 42                         }
 43                     }
 44 
 45                 case State.AuthorizationAsyncBegin:
 46                     {
 47                         Debug.Assert(state != null);
 48                         Debug.Assert(_authorizationContext != null);
 49 
 50                         var filter = (IAsyncAuthorizationFilter)state;
 51                         var authorizationContext = _authorizationContext;
 52 
 53                         _diagnosticSource.BeforeOnAuthorizationAsync(authorizationContext, filter);
 54                         _logger.BeforeExecutingMethodOnFilter(
 55                             FilterTypeConstants.AuthorizationFilter,
 56                             nameof(IAsyncAuthorizationFilter.OnAuthorizationAsync),
 57                             filter);
 58 
 59                         var task = filter.OnAuthorizationAsync(authorizationContext);
 60                         if (task.Status != TaskStatus.RanToCompletion)
 61                         {
 62                             next = State.AuthorizationAsyncEnd;
 63                             return task;
 64                         }
 65 
 66                         goto case State.AuthorizationAsyncEnd;
 67                     }
 68 
 69                 case State.AuthorizationAsyncEnd:
 70                     {
 71                         Debug.Assert(state != null);
 72                         Debug.Assert(_authorizationContext != null);
 73 
 74                         var filter = (IAsyncAuthorizationFilter)state;
 75                         var authorizationContext = _authorizationContext;
 76 
 77                         _diagnosticSource.AfterOnAuthorizationAsync(authorizationContext, filter);
 78                         _logger.AfterExecutingMethodOnFilter(
 79                             FilterTypeConstants.AuthorizationFilter,
 80                             nameof(IAsyncAuthorizationFilter.OnAuthorizationAsync),
 81                             filter);
 82 
 83                         if (authorizationContext.Result != null)
 84                         {
 85                             goto case State.AuthorizationShortCircuit;
 86                         }
 87 
 88                         goto case State.AuthorizationNext;
 89                     }
 90 
 91                 case State.AuthorizationSync:
 92                     {
 93                         Debug.Assert(state != null);
 94                         Debug.Assert(_authorizationContext != null);
 95 
 96                         var filter = (IAuthorizationFilter)state;
 97                         var authorizationContext = _authorizationContext;
 98 
 99                         _diagnosticSource.BeforeOnAuthorization(authorizationContext, filter);
100                         _logger.BeforeExecutingMethodOnFilter(
101                             FilterTypeConstants.AuthorizationFilter,
102                             nameof(IAuthorizationFilter.OnAuthorization),
103                             filter);
104 
105                         filter.OnAuthorization(authorizationContext);
106 
107                         _diagnosticSource.AfterOnAuthorization(authorizationContext, filter);
108                         _logger.AfterExecutingMethodOnFilter(
109                             FilterTypeConstants.AuthorizationFilter,
110                             nameof(IAuthorizationFilter.OnAuthorization),
111                             filter);
112 
113                         if (authorizationContext.Result != null)
114                         {
115                             goto case State.AuthorizationShortCircuit;
116                         }
117 
118                         goto case State.AuthorizationNext;
119                     }
120 
121                 case State.AuthorizationShortCircuit:
122                     {
123                         Debug.Assert(state != null);
124                         Debug.Assert(_authorizationContext != null);
125                         Debug.Assert(_authorizationContext.Result != null);
126 
127                         _logger.AuthorizationFailure((IFilterMetadata)state);
128 
129                         // This is a short-circuit - execute relevant result filters + result and complete this invocation.
130                         isCompleted = true;
131                         _result = _authorizationContext.Result;
132                         return InvokeAlwaysRunResultFilters();
133                     }
134 
135                 case State.AuthorizationEnd:
136                     {
137                         goto case State.ResourceBegin;
138                     }
139 
140                 case State.ResourceBegin:
141                     {
142                         _cursor.Reset();
143                         goto case State.ResourceNext;
144                     }
145 
146                 case State.ResourceNext:
147                     {
148                         var current = _cursor.GetNextFilter<IResourceFilter, IAsyncResourceFilter>();
149                         if (current.FilterAsync != null)
150                         {
151                             if (_resourceExecutingContext == null)
152                             {
153                                 _resourceExecutingContext = new ResourceExecutingContext(
154                                     _actionContext,
155                                     _filters,
156                                     _valueProviderFactories);
157                             }
158 
159                             state = current.FilterAsync;
160                             goto case State.ResourceAsyncBegin;
161                         }
162                         else if (current.Filter != null)
163                         {
164                             if (_resourceExecutingContext == null)
165                             {
166                                 _resourceExecutingContext = new ResourceExecutingContext(
167                                     _actionContext,
168                                     _filters,
169                                     _valueProviderFactories);
170                             }
171 
172                             state = current.Filter;
173                             goto case State.ResourceSyncBegin;
174                         }
175                         else
176                         {
177                             // All resource filters are currently on the stack - now execute the 'inside'.
178                             goto case State.ResourceInside;
179                         }
180                     }
181 
182                 case State.ResourceAsyncBegin:
183                     {
184                         Debug.Assert(state != null);
185                         Debug.Assert(_resourceExecutingContext != null);
186 
187                         var filter = (IAsyncResourceFilter)state;
188                         var resourceExecutingContext = _resourceExecutingContext;
189 
190                         _diagnosticSource.BeforeOnResourceExecution(resourceExecutingContext, filter);
191                         _logger.BeforeExecutingMethodOnFilter(
192                             FilterTypeConstants.ResourceFilter,
193                             nameof(IAsyncResourceFilter.OnResourceExecutionAsync),
194                             filter);
195 
196                         var task = filter.OnResourceExecutionAsync(resourceExecutingContext, InvokeNextResourceFilterAwaitedAsync);
197                         if (task.Status != TaskStatus.RanToCompletion)
198                         {
199                             next = State.ResourceAsyncEnd;
200                             return task;
201                         }
202 
203                         goto case State.ResourceAsyncEnd;
204                     }
205 
206                 case State.ResourceAsyncEnd:
207                     {
208                         Debug.Assert(state != null);
209                         Debug.Assert(_resourceExecutingContext != null);
210 
211                         var filter = (IAsyncResourceFilter)state;
212                         if (_resourceExecutedContext == null)
213                         {
214                             // If we get here then the filter didn't call 'next' indicating a short circuit.
215                             _resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters)
216                             {
217                                 Canceled = true,
218                                 Result = _resourceExecutingContext.Result,
219                             };
220 
221                             _diagnosticSource.AfterOnResourceExecution(_resourceExecutedContext, filter);
222                             _logger.AfterExecutingMethodOnFilter(
223                                 FilterTypeConstants.ResourceFilter,
224                                 nameof(IAsyncResourceFilter.OnResourceExecutionAsync),
225                                 filter);
226 
227                             // A filter could complete a Task without setting a result
228                             if (_resourceExecutingContext.Result != null)
229                             {
230                                 goto case State.ResourceShortCircuit;
231                             }
232                         }
233 
234                         goto case State.ResourceEnd;
235                     }
236 
237                 case State.ResourceSyncBegin:
238                     {
239                         Debug.Assert(state != null);
240                         Debug.Assert(_resourceExecutingContext != null);
241 
242                         var filter = (IResourceFilter)state;
243                         var resourceExecutingContext = _resourceExecutingContext;
244 
245                         _diagnosticSource.BeforeOnResourceExecuting(resourceExecutingContext, filter);
246                         _logger.BeforeExecutingMethodOnFilter(
247                             FilterTypeConstants.ResourceFilter,
248                             nameof(IResourceFilter.OnResourceExecuting),
249                             filter);
250 
251                         filter.OnResourceExecuting(resourceExecutingContext);
252 
253                         _diagnosticSource.AfterOnResourceExecuting(resourceExecutingContext, filter);
254                         _logger.AfterExecutingMethodOnFilter(
255                             FilterTypeConstants.ResourceFilter,
256                             nameof(IResourceFilter.OnResourceExecuting),
257                             filter);
258 
259                         if (resourceExecutingContext.Result != null)
260                         {
261                             _resourceExecutedContext = new ResourceExecutedContext(resourceExecutingContext, _filters)
262                             {
263                                 Canceled = true,
264                                 Result = _resourceExecutingContext.Result,
265                             };
266 
267                             goto case State.ResourceShortCircuit;
268                         }
269 
270                         var task = InvokeNextResourceFilter();
271                         if (task.Status != TaskStatus.RanToCompletion)
272                         {
273                             next = State.ResourceSyncEnd;
274                             return task;
275                         }
276 
277                         goto case State.ResourceSyncEnd;
278                     }
279 
280                 case State.ResourceSyncEnd:
281                     {
282                         Debug.Assert(state != null);
283                         Debug.Assert(_resourceExecutingContext != null);
284                         Debug.Assert(_resourceExecutedContext != null);
285 
286                         var filter = (IResourceFilter)state;
287                         var resourceExecutedContext = _resourceExecutedContext;
288 
289                         _diagnosticSource.BeforeOnResourceExecuted(resourceExecutedContext, filter);
290                         _logger.BeforeExecutingMethodOnFilter(
291                             FilterTypeConstants.ResourceFilter,
292                             nameof(IResourceFilter.OnResourceExecuted),
293                             filter);
294 
295                         filter.OnResourceExecuted(resourceExecutedContext);
296 
297                         _diagnosticSource.AfterOnResourceExecuted(resourceExecutedContext, filter);
298                         _logger.AfterExecutingMethodOnFilter(
299                             FilterTypeConstants.ResourceFilter,
300                             nameof(IResourceFilter.OnResourceExecuted),
301                             filter);
302 
303                         goto case State.ResourceEnd;
304                     }
305 
306                 case State.ResourceShortCircuit:
307                     {
308                         Debug.Assert(state != null);
309                         Debug.Assert(_resourceExecutingContext != null);
310                         Debug.Assert(_resourceExecutedContext != null);
311 
312                         _logger.ResourceFilterShortCircuited((IFilterMetadata)state);
313 
314                         _result = _resourceExecutingContext.Result;
315                         var task = InvokeAlwaysRunResultFilters();
316                     

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

-Advertisement-
Play Games
更多相關文章
  • 諸暨牌頭鎮山下周村壽連軍 2016年2月1日 諸暨牌頭鎮山下周村村支書壽連軍伙同江蘇海安人史發龍在根本沒有承包到諸暨凱信大廈工程的事實情況下,利用工程分包合同騙取我本人吳善如工程保證金150萬元! 江蘇海安市公安局已經將壽連軍 史發龍 以及其子壽鵬飛立案為合同詐騙案! 經過百度搜索查詢 壽連軍以及其 ...
  • 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace ConsoleApp ...
  • ASP.NET Boilerplate入門踩坑/ABP Module Zero/.NET Core 2.2 + Vue.js ...
  • 上面的例中,當打斷點調試時,斷點斷住時, Attr1屬性的get塊就會執行一次。 兩個斷點加在邏輯中對Attr1的訪問,最後發現CTest get Attr1.列印了3次。 得到的結論是:多餘的2次列印是由於斷點使得程式中斷引發的,中斷後調試器需要獲取Attr1的數值,對get塊進行調用。 不調試直 ...
  • Windows Service這一塊並不複雜,但是註意事項太多了,網上資料也很凌亂,偶爾自己寫也會丟三落四的。所以本文也就產生了,本文不會寫複雜的東西,完全以基礎應用的需求來寫,所以不會對Windows Service寫很深入。 本文介紹瞭如何用C#創建、安裝、啟動、監控、卸載簡單的Windows ...
  • 在這篇《MVC 5使用TempData Object跨視圖傳遞數據》https://www.cnblogs.com/insus/p/3378016.html中,已經在評論回覆網友:網上查找到的資料: ...
  • 在本節中,您將看到ASP.NET Core應用程式如何運行的,從請求URL開始到頁面呈現在瀏覽器中。 為此,您將看到 一個HTTP請求在Web伺服器中是如何被處理的、ASP.NET Core如何擴展該過程以創建動態頁面。 ...
  • 前言: 對於一個簡單的資料庫應用,由於對於資料庫的訪問不是很頻繁。這時可以簡單地在需要訪問資料庫時,就新創建一個連接,用完後就關閉它,這樣做也不會帶來什麼明顯的性能上的開銷。但是對於一個複雜的資料庫應用,情況就完全不同了。頻繁的建立、關閉連接,會極大的減低系統的性能,因為對於連接的使用成了系統性能的 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...