asp.net core 之中間件

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

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>();

  

 


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

更多相關文章
  • 因為重裝了系統,重新配置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 ...
一周排行
  • 該方式是直接對屏幕進行截圖操作UserControl chartContainPanel = new UserControl();Graphics graph = chartContainPanel.CreateGraphics();Size s = chartContainPanel.Size;B... ...
  • dotnetcore3.1 WPF 中使用依賴註入 Intro 在 ASP.NET Core 中預設就已經集成了依賴註入,最近把 "DbTool" 遷移到了 WPF dotnetcore 3.1, 在 WPF 中我們也希望能夠使用依賴註入,下麵來介紹一下如何在 WPF dotnetcore3.1 中 ...
  • 原來的C 程式都有Main的,現在用vs新建一個Wpf項目,啟動似乎變成App.xmal,前期項目中為了獲取啟動參數,很是折騰了一番: 1.先是修改App.xaml,添加StartUp事件 2.然後編輯Application_Startup,判斷e.Args數組 總感覺跟又臭又長的裹腳布一樣,不爽。 ...
  • 冒泡排序原理:(升序)通過當前位置數和後一個位置數進行比較 如果當前數比後一個數大 則交換位置, 完成後 比較基數的位置變成下一個數。直到數組末尾,當程式運行完第一遍 最大的數已經排序到最後一個位置了。次數可以減少迴圈數不用管最後一個數 降序排序同理 不過是把比較方式變成判斷當前數是否小於下一個數 ...
  • 一、前言 這方面的資料很多,重覆的寫沒必要,但是最近一直在學習身份驗證和授權相關東東,為了成體系還是寫一篇,主要是從概念上理解identity系統。 參考:https://www.cnblogs.com/r01cn/p/5179506.html 二、概述 幾乎所有系統都包含用戶、角色、許可權、登錄、註 ...
  • 首先我們使用最簡單的模板案例,裡面有一個Counter計數器,你可以在創建模板中找到。 首先需要設置運行調試方式為IIS Express。這意味著,MAC可能不能使用調試。 然後開啟運行而不調試(Ctrl+F5) 按Shift + Alt + D,會出現一個新的頁面。 如果你想用Chrome調試,復 ...
  • 實體映射時,遇到複雜類型,可選擇下述方法處理: NotMapped,跳過映射 在複雜類型上聲明 [Owned],但僅限該複雜類型是全部由簡單值類型組成的 自定義序列化方法 示例: IPInfo使用了owned,對IPEndPoint使用自定義序列化,對VersionInfo使用JSON序列化 @@@... ...
  • .NET Core 3 Web Api Cors fetch 一直 307 Temporary Redirect 繼上一篇 ".net core 3 web api jwt 一直 401" 為添加 所述的坑後, 本次為添加 ,又踩坑了。 自從 .NET Core 2.2 之後,CORS跨域配置代碼發 ...
  • 在前一章已經學習過WPF動畫的第一條規則——每個動畫依賴於一個依賴項屬性。然而,還有另一個限制。為了實現屬性的動態化(換句話說,使用基於時間的方式改變屬性的值),需要有支持相應數據類型的動畫類。例如,Button.Width屬性使用雙精度數據類型。為實現屬性的動態化,需要使用DoubleAnimat ...
  • WPF dotnet core 3.1 基於 `Microsoft.Extensions.Localization` 實現基本的多語言支持 ...
x