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

  

 


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

更多相關文章
  • 前提 入行已經7,8年了,一直想做一套漂亮點的自定義控制項,於是就有了本系列文章。 GitHub:https://github.com/kwwwvagaa/NetWinformControl 碼雲:https://gitee.com/kwwwvagaa/net_winform_custom_contr ...
  • 背景:臨時提供一個簡單的網頁,供其他人瀏覽資料庫(Oracel、MSSQL)的某些數據,並導出Excel。 ...
  • 依賴註入主要是一種結構性的模式,註重的是類與類之間的結構,它要達到的目的就是設計原則中最少知道和合成復用的原則,減少內部依賴,履行單一職責,最終就是強解耦。依賴註入目前最好的實現就是依賴註入容器。 Unity是微軟Patterns & Practices團隊所開發的一個輕量級的,並且可擴展的依賴註入 ...
  • 場景 DevExpress的PanelControl常用進行窗體頁面的佈局。 一般是拖拽一個PannelControl,然後是再拖拽其他控制項。 如果是由代碼生成控制項並控制佈局的話,怎樣實現。 關註公眾號 霸道的程式猿 獲取編程相關電子書、教程推送與免費下載。 實現 比如說要在PanelContrl中 ...
  • 通過 abp(net core)+easyui+efcore實現倉儲管理系統——菜單-上 (十六)這篇文章,我們已經瞭解了ABP中的菜單相關的類及類的屬性與方法,接下我們通過實例來實現一個動態載入菜單的功能。動態菜單是我們在abp(net core)+easyui+efcore實現倉儲管理系統——領... ...
一周排行
  • 首先給大家介紹一下序列化是用來乾什麼的,他為什麼出現 序列化這種技術說到底其實就是把臨時數據保存在電腦上。大家都知道對於程式而言對象是一種稍縱即逝的數據,不僅僅是程式重啟、電腦重啟,就連內部函數的變化也有可能導致對象的消失,但是總有一些對象是大家不想讓其隨意消失的並且想在下一次開啟程式的時候進行載入 ...
  • 一、前言 在.Net Framework框架有專門獲取webconfig配置的方法供我們使用,但是在.Net Core或者.Net Standard中沒有可以直接使用的方法來獲取配置文件信息,下麵就來實現獲取配置信息。 二、獲取配置信息的實現 在.Net Core中,他的配置信息的載體是一個json ...
  • 你一定看過這篇文章 《進擊的 Java ,雲原生時代的蛻變》, 本篇文章的靈感來自於這篇文章。北京時間9.24 就將正式發佈.NET Core 3.0, 所以寫下這篇文章讓大家全面認識.NET Core。.NET 生態系統是一個不斷變化的生態圈,我相信它正在朝著一個偉大的方向發展。正好 最近 Inf... ...
  • 參考文檔:Dapper one to many Table C Code pulic List GetPersons(){ var sql = @"SELECT 1 AS Id, 'Daniel Dennett' AS Name, 1942 AS Born, 1 AS CountryId, 'Uni ...
  • 非標設備多相機流水線模式緩存圖片(C/S客戶端,c 開發語言) ​ 本文所說流水線方式下存儲圖像是在軟體測量周期慢於圖片周期前提下講解的,如果軟體一直在等待圖片數據,邏輯就沒有那麼複雜。 1、非標設備項目,常規模式測量流程 常規模式下,相機採集圖像信號由上位機控制(無論軟觸發、硬觸發)。每個周期內的 ...
  • 簡單創建.NET Core WebApi:https://www.cnblogs.com/yanbigfeg/p/9197375.html 登陸驗證四種方式:https://www.cnblogs.com/zuowj/p/5123943.html 解決跨域的8種方法:https://blog.csd ...
  • 最近有個需求就是網頁表格裡面的數據導出到excel 於是從各位前輩的博客園搜了搜demo 大部分非為兩類 都是用的插件NPOI和Eppluse ,因此在這裡就介紹Eppluse 用法,還有就是在博客的時候 好多有留言說想看從資料庫裡面的數據進行導入 而不是寫死的,所以我就以我的案例給大家分享下用法( ...
  • 表達式樹練習實踐:C 值類型、引用類型、泛型、集合、調用函數 [TOC] 一,定義變數 C 表達式樹中,定義一個變數,使用 。 創建變數結點的方法有兩種, 兩種方式都是生成 類型 和 都具有兩個重載。他們創建一個 ParameterExpression節點,該節點可用於標識表達式樹中的參數或變數。 ...
  • 就像是.NET Framework WebApi與.NET Core WebApi一樣,.NET Framework MVC與.NET Core MVC的區別,也是框架的之間的區別。本系列先首先從.NET Framework MVC介紹,後面再去介紹.NET Core MVC 狹義MVC: MVC是 ...
  • 緩存的實現 我們不是做第三方比如Redis等的緩存實現,而是根據實際情況,基於C#上做一些環境變數的保存,方便項目使用。 1、系統全局變數 很多時候,在系統運行開始,需要對系統的運行參數進行保存,以便供全局使用。 代碼如下: 這裡使用一個靜態變數的Dictionary來進行保存,所有項目均可以直接獲 ...
x