asp.net core 之中間件

来源:https://www.cnblogs.com/liujiabing/archive/2019/09/10/11498163.html
-Advertisement-
Play Games

Http請求資源的過程可以看成一個管道:“Pipe”,並不是所有的請求都是合法的、安全的,其於功能、性能或安全方面的考慮,通常需要在這管道中裝配一些處理程式來篩選和加工這些請求。這些處理程式就是中間件。 中間件之間的調用順序就是添加中間件組件的順序,調用順序以於應用程式的安全性、性能、和功能至關重要 ...


Http請求資源的過程可以看成一個管道:“Pipe”,並不是所有的請求都是合法的、安全的,其於功能、性能或安全方面的考慮,通常需要在這管道中裝配一些處理程式來篩選和加工這些請求。這些處理程式就是中間件。

 

 

 中間件之間的調用順序就是添加中間件組件的順序,調用順序以於應用程式的安全性、性能、和功能至關重要。

如UserDeveloperExceptionPage中間件需要放在第一個被調用位置,因為回應的最後一步需要它來處理異常,如跳轉到異常頁面這樣的操作。UseStaticFiles需要放在UseMvc前,因為Mvc中間件做了路由處理,(wwwroot)文件夾里的圖片,js,html等靜態資源路徑沒有經過路由處理,mvc中間件會直接返回404。

可以使用IApplicationBuilder創建中間件,IApplicationBuilder有依賴在StartUp.Configure方法參數中,可以直接使用。一般有三種方法使用中間件:use,run,map。其中如果用run創建中間件的話,第一個run就會中止表示往下傳遞,後邊的use,run,map都不會起到任何作用。

Run:終端中間件,會使管道短路。

           app.Run(async context =>
            {
                await context.Response.WriteAsync("first run");
            });
            app.Run(async context =>
            {
                await context.Response.WriteAsync("second run");
            });        

 只會輸出"first run"。 

Map:約束終端中間件,匹配短路管道.匹配條件是HttpContext.Request.Path和預設值

           app.Map("/map1", r =>
            {
                r.Run(async d =>
                {
                    await d.Response.WriteAsync("map1");
                });
            });

            app.Map("/map2", r =>
            {
                r.Run(async d =>
                {
                    await d.Response.WriteAsync("map2");
                });
            });

 訪問 https://localhost:5002/map1 返回"map1",訪問https://localhost:5002/map2時訪問"map2"。都不符合時繼續往下走。

Map有個擴展方法MapWhen,可以自定義匹配條件:

  app.MapWhen(context => context.Request.QueryString.ToString().ToLower().Contains("sid"), r =>
            {
                r.Run(async d =>
                {
                    await d.Response.WriteAsync("map:"+d.Request.QueryString);
                });
            });

 Map和Run方法創建的都是終端中間件,無法決定是否繼續往下傳遞,只能中斷。

 使用中間件保護圖片資源

 為防止網站上的圖片資源被其它網頁盜用,使用中間件過濾請求,非本網站的圖片請求直接返回一張通用圖片,本站請求則往下傳遞,所以run和map方法不適用,只能用use,use方法可以用委托決定是否繼續往下傳遞。

實現原理:模式瀏覽器請求時,一般會傳遞一個名稱為Referer的Header,html標簽<img>等是模擬瀏覽器請求,所以會帶有Referer頭,標識了請求源地址。可以利用這個數據與Request上下文的Host比較判斷是不是本站請求。有以下幾點需要註意:https訪問http時有可能不會帶著Referer頭,但http訪問https,https訪問http時都會帶,所以如果網站啟用了https就能一定取到這個Header值。還有一點Referer這個單詞是拼錯了的,正確的拼法是Refferer,就是早期由於http標準不完善,有寫Refferer的,有寫Referer的。還有就是瀏覽器直接請求時是不帶這個Header的。這個中間件必需在app.UseStaticFiles()之前,因為UseStaticFiles是一個終端中間件,會直接返回靜態資源,不會再往下傳遞請求,而圖片是屬於靜態資源。

  app.Use(async (context,next) => {
                //是否允許空Referer頭訪問
                bool allowEmptyReffer=true;
                //過濾圖片類型
                string[] imgTypes = new string[] { "jpg", "ico", "png" };
                //本站主機地址
                string oriUrl = $"{context.Request.Scheme}://{context.Request.Host.Value}".ToLower();
                //請求站地址標識
                var reffer = context.Request.Headers[HeaderNames.Referer].ToString().ToLower();
                reffer = string.IsNullOrEmpty(reffer) ? context.Request.Headers["Refferer"].ToString().ToLower():reffer;
                //請求資源尾碼
                string pix = context.Request.Path.Value.Split(".").Last();
                if (imgTypes.Contains(pix) && !reffer.StartsWith(oriUrl)&&(string.IsNullOrEmpty(reffer)&&!allowEmptyReffer))
                {
                    //不是本站請求返回特定圖片
                    await context.Response.SendFileAsync(Path.Combine(env.WebRootPath, "project.jpg"));
                }
                //本站請求繼續往下傳遞
                await next();
            });

 這樣配置雖然可以工作的,但也是有問題的,因為直接發送文件,沒有顧得上HTTP的請求狀態設置,SendFile後就沒管了,當然,本站請求直接next的請求沒有問題,因為有下一層的中間件去處理狀態碼。下麵是列印出的異常

fail: Microsoft.AspNetCore.Server.Kestrel[13]
      Connection id "0HLPLOP3KV1UI", Request id "0HLPLOP3KV1UI:00000001": An unhandled exception was thrown by the application.
System.InvalidOperationException: StatusCode cannot be set because the response has already started.
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ThrowResponseAlreadyStartedException(String value)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.set_StatusCode(Int32 value)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.Microsoft.AspNetCore.Http.Features.IHttpResponseFeature.set_StatusCode(Int32 value)
   at Microsoft.AspNetCore.Http.Internal.DefaultHttpResponse.set_StatusCode(Int32 value)
   at Microsoft.AspNetCore.StaticFiles.StaticFileContext.ApplyResponseHeaders(Int32 statusCode)
   at Microsoft.AspNetCore.StaticFiles.StaticFileContext.SendStatusAsync(Int32 statusCode)
   at Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context)
   at IdentityMvc.Startup.<>c.<<Configure>b__8_0>d.MoveNext() in E:\identity\src\IdentityMvc\Startup.cs:line 134
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
      Request finished in 74.2145ms 200

狀態碼肯定要管,做事不能做一半,這樣改一下

app.Use( (context, next) =>
            {
                //是否允許空Referer頭訪問
                bool allowEmptyReffer = false;
                //過濾圖片類型
                string[] imgTypes = new string[] { "jpg", "ico", "png" };
                //本站主機地址
                string oriUrl = $"{context.Request.Scheme}://{context.Request.Host.Value}".ToLower();
                //請求站地址標識
                var reffer = context.Request.Headers[HeaderNames.Referer].ToString().ToLower();
                reffer = string.IsNullOrEmpty(reffer) ? context.Request.Headers["Refferer"].ToString().ToLower() : reffer;
                //請求資源尾碼
                string pix = context.Request.Path.Value.Split(".").Last();
                if (imgTypes.Contains(pix) && !reffer.StartsWith(oriUrl) && (string.IsNullOrEmpty(reffer) && !allowEmptyReffer))
                {
                    //不是本站請求返回特定圖片
                    context.Response.SendFileAsync(Path.Combine(env.WebRootPath, "project.jpg")).Wait();
                    return Task.CompletedTask;
                }
                //本站請求繼續往下傳遞
                return  next();
            });

 正常了。

如果中間件的邏輯複雜,直接放在StartUp類中不太合適,可以把中間件獨立出來,類似UseStaticFiles這樣的方式。新建一個類,實現自IMiddleware介面 public class ProjectImgMiddleware : IMiddleware

 public class ProjectImgMidleware:IMiddleware
    {
        public Task InvokeAsync(HttpContext context, RequestDelegate next)
        {
            //是否允許空Referer頭訪問
            bool allowEmptyReffer = true;
            //過濾圖片類型
            string[] imgTypes = new string[] { "jpg", "ico", "png" };
            //本站主機地址
            string oriUrl = $"{context.Request.Scheme}://{context.Request.Host.Value}".ToLower();
            //請求站地址標識
            var reffer = context.Request.Headers[HeaderNames.Referer].ToString().ToLower();
            reffer = string.IsNullOrEmpty(reffer) ? context.Request.Headers["Refferer"].ToString().ToLower() : reffer;
            //請求資源尾碼
            string pix = context.Request.Path.Value.Split(".").Last();
            if (imgTypes.Contains(pix) && !reffer.StartsWith(oriUrl) && (string.IsNullOrEmpty(reffer) && !allowEmptyReffer))
            {
                //不是本站請求返回特定圖片
                 context.Response.SendFileAsync(Path.Combine("E:\\identity\\src\\IdentityMvc\\wwwroot", "project.jpg")).Wait();
                return Task.CompletedTask;
            }
            //本站請求繼續往下傳遞
            return next(context);
        }
    }

 再新建一個靜態類,對IApplicationBuilder進行擴寫

public static class ProjectImgMiddlewareExtensions
    {
        public static IApplicationBuilder UseProjectImg(this IApplicationBuilder builder) {
            return builder.UseMiddleware<ProjectImgMiddleware >(); } }

  在StartUp類中引用ProjectImgMiddlewareExtensions就可以使用UseProjectImg

 app.UseProjectImg();

 這時如果直接運行會報InvalidOperationException

 

 

 字面意思是從依賴庫中找不到ProjectImgMiddleware這個類,看來需要手動添加這個類的依賴

  services.AddSingleton<ProjectImgMiddleware>();

再次運行就沒有問題了,為什麼呢,看了一下Asp.net core中UseMiddleware這個對IApplicationBuilder的擴寫方法源碼,如果實現類是繼承自IMiddleware介面,執行的是Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.UseMiddlewareInterface方法,這個方法找到中間件實例採用的是IServiceCollection.GetServices方式,是需要手動添加依賴的,如果中間件的實現類不是繼承自IMiddleware介面,是用ActivatorUtilities.CreateInstance根據類型創建一個新的實例,是不需要手動添加依賴的。

private static IApplicationBuilder UseMiddlewareInterface(IApplicationBuilder app, Type middlewareType)
        {
            return app.Use(next =>
            {
                return async context =>
                {
                    var middlewareFactory = (IMiddlewareFactory)context.RequestServices.GetService(typeof(IMiddlewareFactory));
                    if (middlewareFactory == null)
                    {
                        // No middleware factory
                        throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoMiddlewareFactory(typeof(IMiddlewareFactory)));
                    }

                    var middleware = middlewareFactory.Create(middlewareType);
                    if (middleware == null)
                    {
                        // The factory returned null, it's a broken implementation
                        throw new InvalidOperationException(Resources.FormatException_UseMiddlewareUnableToCreateMiddleware(middlewareFactory.GetType(), middlewareType));
                    }

                    try
                    {
                        await middleware.InvokeAsync(context, next);
                    }
                    finally
                    {
                        middlewareFactory.Release(middleware);
                    }
                };
            });
        }

var middlewareFactory = (IMiddlewareFactory)context.RequestServices.GetService(typeof(IMiddlewareFactory));沒有手動添加依賴,肯定是找不到實例實例的。

中間件中的依賴註入。

回顧一下ASP.NET CORE支持的依賴註入對象生命周期

1,Transient 瞬時模式,每次都是新的實例

2,Scope 每次請求,一個Rquest上下文中是同一個實例

3,Singleton 單例模式,每次都是同一個實例

所有中間件的實例聲明只有一Application啟動時聲明一次,而中間件的Invoke方法是每一次請求都會調用的。如果以Scope或者Transient方式聲明依賴的對象在中間件的屬性或者構造函數中註入,中間件的Invoke方法執行時就會存在使用的註入對象已經被釋放的危險。所以,我們得出結論:Singleton依賴對象可以在中間件的構造函數中註入。在上面的實例中,我們找到返回特定圖片文件路徑是用的絕對路徑,但這個是有很大的問題的,如果項目地址變化或者發佈路徑變化,這程式就會報異常,因為找不到這個文件。

await context.Response.SendFileAsync(Path.Combine("E:\\identity\\src\\IdentityMvc\\wwwroot", "project.jpg"));

所以,這種方式不可取,asp.net core有一個IHostingEnvironment的依賴對象專門用來查詢環境變數的,已經自動添加依賴,可以直接在要用的地方註入使用。這是一個Singleton模式的依賴對象,我們可以在中間件對象的構造函數中註入

 readonly IHostingEnvironment _env;
        public ProjectImgMiddleware(IHostingEnvironment env)
        {
            _env = env;
        }

 把獲取wwwroot目錄路徑的代碼用 IHostingEnvironment  的實現對象獲取

  context.Response.SendFileAsync(Path.Combine(_env.WebRootPath, "project.jpg"));

 

這樣就沒有問題了,不用關心項目路徑和發佈路徑。

Singleton模式的依賴對象可以從構造函數中註入,但其它二種呢?只能通過Invoke函數參數傳遞了。但IMiddle介面已經約束了Invoke方法的參數類型和個數,怎麼添加自己的參數呢?上邊說過中間件實現類可以是IMiddleware介面子類,也可以不是,它是怎麼工作的呢,看下UseMiddleware源碼

public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware, params object[] args)
        {
            if (typeof(IMiddleware).GetTypeInfo().IsAssignableFrom(middleware.GetTypeInfo()))
            {
                // IMiddleware doesn't support passing args directly since it's
                // activated from the container
                if (args.Length > 0)
                {
                    throw new NotSupportedException(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware)));
                }

                return UseMiddlewareInterface(app, middleware);
            }

            var applicationServices = app.ApplicationServices;
            return app.Use(next =>
            {
                var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public);
                var invokeMethods = methods.Where(m =>
                    string.Equals(m.Name, InvokeMethodName, StringComparison.Ordinal)
                    || string.Equals(m.Name, InvokeAsyncMethodName, StringComparison.Ordinal)
                    ).ToArray();

                if (invokeMethods.Length > 1)
                {
                    throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName, InvokeAsyncMethodName));
                }

                if (invokeMethods.Length == 0)
                {
                    throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName, InvokeAsyncMethodName, middleware));
                }

                var methodInfo = invokeMethods[0];
                if (!typeof(Task).IsAssignableFrom(methodInfo.ReturnType))
                {
                    throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, InvokeAsyncMethodName, nameof(Task)));
                }

                var parameters = methodInfo.GetParameters();
                if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext))
                {
                    throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, InvokeAsyncMethodName, nameof(HttpContext)));
                }

                var ctorArgs = new object[args.Length + 1];
                ctorArgs[0] = next;
                Array.Copy(args, 0, ctorArgs, 1, args.Length);
                var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs);
                if (parameters.Length == 1)
                {
                    return (RequestDelegate)methodInfo.CreateDelegate(typeof(RequestDelegate), instance);
                }

                var factory = Compile<object>(methodInfo, parameters);

                return context =>
                {
                    var serviceProvider = context.RequestServices ?? applicationServices;
                    if (serviceProvider == null)
                    {
                        throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));
                    }

                    return factory(instance, context, serviceProvider);
                };
            });
        }

  如果中間件實現類是Imiddleware介面子類,則執行UserMiddlewareInterface方法,上面已經說過了,可以在構造函數中註入對象,但不支持Invoke參數傳遞。如果不是IMiddlewware子類,則用ActivatorUtilities

.CreateIntance方法創建實例,關於ActivatorUtilities可以看看官方文檔,作用就是允許註入容器中沒有服務註冊的對象。

 所以,如果中間件實現類沒有實現IMiddleware介面,是不需要手動添加依賴註冊的。那Invoke的參數傳遞呢?源碼是這樣處理的

    private static object GetService(IServiceProvider sp, Type type, Type middleware)
        {
            var service = sp.GetService(type);
            if (service == null)
            {
                throw new InvalidOperationException(Resources.FormatException_InvokeMiddlewareNoService(type, middleware));
            }

            return service;
        }

是通過當前請求上下文中的IServiceProvider.GetService獲取Invoke方法傳遞的參數實例。所以如果中間件實現類沒有實現IMiddleware介面,支持構造函數註入,也支持Invoke參數傳遞,但由於沒有IMiddleware介面的約束,一定要註意以下三個問題:

1,執行方法必需是公開的,名字必需是Invoke或者InvokeAsync。

 源碼通過反射找方法就是根據這個條件

  var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public);
                var invokeMethods = methods.Where(m =>
                    string.Equals(m.Name, InvokeMethodName, StringComparison.Ordinal)
                    || string.Equals(m.Name, InvokeAsyncMethodName, StringComparison.Ordinal)
                    ).ToArray();

2,執行方法必需返回Task類型,因為UseMiddleware實際上是Use方法的加工,Use方法是要求返回RequestDelegate委托的。RequestDelegate委托約束了返回類型

源碼判斷:

if (!typeof(Task).IsAssignableFrom(methodInfo.ReturnType))
                {
                    throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, InvokeAsyncMethodName, nameof(Task)));
                }
if (parameters.Length == 1)
                {
                    return (RequestDelegate)methodInfo.CreateDelegate(typeof(RequestDelegate), instance);
                }

RequestDelegate定義 

public delegate Task RequestDelegate(HttpContext context);

3,不管你Invoke方法有多少個參數,第一個參數類型必需是HttpContext

if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext))
{
throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, InvokeAsyncMethodName, nameof(HttpContext)));
}

根據這三點原則,我們改造一下上面的實例,加入日誌記錄功能。去掉ProjectImgMiddleware的IMiddleware繼承實現關係

 public class ProjectImgMiddleware 

 由於RequestDelegate和IHostingEnvironment是Singleton依賴註冊,所以可以在構造函數中註入

 readonly IHostingEnvironment _env;
        readonly RequestDelegate _next;
        public ProjectImgMiddleware(RequestDelegate next, IHostingEnvironment env)
        {
            _env = env;
            _next=next;
        }

要加入日誌功能,可以在Invoke方法中參數傳遞。註意返回類型,方法名稱,第一個參數類型這三個問題

  

 public  Task InvokeAsync(HttpContext context, ILogger<ProjectImgMiddleware> logger)
        {
            //是否允許空Referer頭訪問
            bool allowEmptyReffer = false;
            //過濾圖片類型
            string[] imgTypes = new string[] { "jpg", "ico", "png" };
            //本站主機地址
            string oriUrl = $"{context.Request.Scheme}://{context.Request.Host.Value}".ToLower();
            //請求站地址標識
            var reffer = context.Request.Headers[HeaderNames.Referer].ToString().ToLower();
            reffer = string.IsNullOrEmpty(reffer) ? context.Request.Headers["Refferer"].ToString().ToLower() : reffer;
            //請求資源尾碼
            string pix = context.Request.Path.Value.Split(".").Last();
            if (imgTypes.Contains(pix) && !reffer.StartsWith(oriUrl) && (string.IsNullOrEmpty(reffer) && !allowEmptyReffer))
            {
                //日誌記錄
                logger.LogDebug($"來自{reffer}的異常訪問");
                //不是本站請求返回特定圖片
                 context.Response.SendFileAsync(Path.Combine(_env.WebRootPath, "project.jpg")).Wait();
                return Task.CompletedTask;
            }
            //本站請求繼續往下傳遞
            return  _next(context);
        }

  

 由於不是Imiddleware的實現類,所以可以註釋掉手動註冊依賴。

 // services.AddSingleton<ProjectImgMiddleware>();

如果沒有添加預設的log功能,在Program.CreateWebHostBuilder中添加log功能

 public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
            .ConfigureLogging(config =>
            {
                config.AddConsole();
            })
                .UseStartup<Startup>();

  

 


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

-Advertisement-
Play Games
更多相關文章
  • 因為重裝了系統,重新配置JDK環境變數,過程中遇到一些問題,分享一下。 本文圖片較多,適合新手 -- ( : 工具: Window 10 JDK1.8,官網或百度雲很好找 JDK1.8,官網或百度雲很好找 步驟: 一、安裝 單擊安裝包,進入以下界面: 直接點擊下一步。 直接點擊下一步。 安裝路徑建議 ...
  • 我不打算解釋什麼是 ,也不解釋為什麼要使用它。我希望你已經在其他地方瞭解過,如果沒有,你可以使用 去搜索它。在本文中,我將告訴您如何使用專門針對 和`RxJava`的響應式編程。讓我們開始吧。 1.預備知識 在你繼續閱讀之前,我希望你能理解如何使用 和`RxJava REST API`。 如果不能, ...
  • 上次在 asp.net core 從單機到集群 一文中提到存儲還不支持分散式,並立了一個 flag > 基於 github 或者 開源中國的碼雲實現一個 storage 於是這兩天就來填坑了。。 ...
  • 一、static關鍵字 下麵我設計了一個房貸利率上浮類(用來計算房貸利率上浮多少): 上面例子的問題在於基準利率這個屬性是所有房貸利率上浮對象共用的屬性,而不是每個房貸利率上浮對象都擁有一個基準利率。所以要把基準利率這個屬性設置成共用的需要使用static關鍵字,第二版房貸利率上浮類: 靜態自動屬性 ...
  • 在java的spring中有自動註入功能,使得代碼變得更加簡潔靈活,所以想把這個功能移植到c#中,接下來逐步分析實現過程 1.使用自動註入場景分析 在asp.net mvc中,無論是什麼代碼邏輯分層,最終的表現層為Controller層,所以我們註入點就是在Controller中,這裡我們需要替換默 ...
  • EF 6及以前的版本是預設支持延遲載入(Lazy Loading)的,早期的EF Core中並不支持,必須使用Include方法來支持導航屬性的數據載入。 當然在 EF Core 2.1 及之後版本中已經引入了延遲載入功能,詳細實現原理可以查看官網( "傳送門" )。 下麵記錄一下,分別使用Incl ...
  • 按照目前的軟體開發發展趨勢中,不管是前後端分離還是提供數據服務,WebApi使用的越來越廣泛,而且.NET Core也是我們.NET開發人員未來發展的趨勢,所以說學會使用.NET Core Api是非常有必要的。 本人作為一個.NET菜鳥,正在慢慢的學習中,將學到的一步一步記錄下來。 一、創建項目 ...
  • 官網地址:https://framework7.io/docs/autocomplete.html#autocomplete-parameters 效果圖: <meta charset="UTF-8"><meta name="viewport" content="width=device-width ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...