### 前言 當我們編寫 C# 代碼時,經常需要處理大量的數據集合。在傳統的方式中,我們往往需要先將整個數據集合載入到記憶體中,然後再進行操作。但是如果數據集合非常大,這種方式就會導致記憶體占用過高,甚至可能導致程式崩潰。 C# 中的`yield return`機制可以幫助我們解決這個問題。通過使用`y ...
一、簡介
Net Core跨平臺項目開發多了,總會遇到各種各樣的問題,我就遇到了一個這樣的問題,不能訪問 Cannot access a disposed object 錯誤,經過自己多方努力,查閱資料,終於找到瞭解決辦法,引發這個問題的原因大多數是多次讀取請求Body流造成的,需要換一種獲取請求Body流方法,不能使用StreamRreader方式,使用Body.CopyTo(ms)方法。
我使用的環境:Visual Studio 2022
開發語言:C#
開發框架:Asp.Net Core Mvc
DotNet版本:Net 6.0
遇到問題是好事,說明自己還有不足,那就解決它,時間長了,技術和知識也就積累了。其實解決方法不難,話不多,直接上解決方案。
二、解決方案的具體實現。
解決方法很簡單,不需要做過多解釋,直接找個配置和編碼就可以了,我貼完整源碼,是便於以後查閱,不喜勿噴。
總共三步:紅色字體寫好了操作步驟(說明一下,紅色字體是要解決方法,其他不要關註,把整個代碼貼出來,是為了以後查閱)
using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Server.Kestrel.Core; using OpticalTrap.Framework.DataAccessors; using OpticalTrap.Web.Facade.Extensions.Filters; using OpticalTrap.Web.Facade.Utilities; using OpticalTrap.Web.ServiceManager; var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllersWithViews(option => { option.Filters.Add(typeof(GlobalAuthorizationFilterAttribute)); option.Filters.Add(typeof(GlobalOperationLogFilterAttribute)); }).AddXmlSerializerFormatters(); #region 第一步:配置可以同步請求讀取流數據 builder.Services.Configure<KestrelServerOptions>(k => k.AllowSynchronousIO = true) .Configure<IISServerOptions>(k => k.AllowSynchronousIO = true); #endregion #region 配置日誌 builder.Logging.AddLog4Net("ConfigFiles/log4net.config"); #endregion #region 配置 Session builder.Services.AddSession(); #endregion #region 配置資料庫 builder.Services.AddTransientSqlSugar(builder.Configuration["ConnectionStrings:DefaultConnectionString"]); #endregion #region 配置區域 builder.Services.Configure<RazorViewEngineOptions>(option => { option.AreaViewLocationFormats.Clear(); option.AreaViewLocationFormats.Add("/Areas/{2}/Views/{1}/{0}.cshtml"); option.AreaViewLocationFormats.Add("/Areas/{2}/Views/Shared/{0}.cshtml"); option.AreaViewLocationFormats.Add("/Views/Shared/{0}.cshtml"); }); #endregion #region 配置服務實例 builder.Services.AddBusinessServices(); builder.Services.AddUtilityServices(); builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); builder.Services.AddSingleton<SessionCacheObjectProvider>(); builder.Services.AddTransient<AuthorizedDataGridGeneratorWrapper>(); builder.Services.AddSingleton<PageNumberGenerator>(); #endregion #region 認證設置 builder.Services.AddAuthentication(option => { option.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; option.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme; option.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; option.DefaultForbidScheme = CookieAuthenticationDefaults.AuthenticationScheme; option.DefaultSignOutScheme = CookieAuthenticationDefaults.AuthenticationScheme; }).AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options => { options.LoginPath = "/Validation/Index"; options.LogoutPath = "/Validation/Logout"; options.AccessDeniedPath = "/Validation/Index"; options.Cookie.HttpOnly = true; options.ClaimsIssuer = "Cookie"; }); #endregion var app = builder.Build(); //第二步:啟用倒帶, 在發生異常時, 可以通過過濾器獲取post參數 app.Use((context, next) => { context.Request.EnableBuffering(); return next(context); }); if (app.Environment.IsProduction()) { app.UseStatusCodePagesWithReExecute("/ErrorHandler/HttpStatusCode", "?statusCode={0}"); app.UseExceptionHandler("/ErrorHandler/ExceptionHandler"); } else { app.UseDeveloperExceptionPage(); } app.UseStaticFiles(); app.UseSession(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllerRoute("defaultAreaRoute", "{area:exists}/{controller=Home}/{action=Index}/{id?}"); app.MapControllerRoute("defaultRoute", "{controller=Home}/{action=Index}/{id?}"); app.Run();
我寫了一個過濾器(實現日誌功能),實現的是IActionFilter 過濾器,在過濾器中,獲取Post請求Body的Context使用下麵方式獲取強調(強調,關註紅色字體,其他無關,這是我寫的日誌功能,貼全部代碼,便於以後查閱,不喜勿噴)
1 using Microsoft.AspNetCore.Mvc.Filters; 2 using Newtonsoft.Json; 3 using OpticalTrap.Framework.Loggings; 4 using OpticalTrap.Web.ConstProvider; 5 using OpticalTrap.Web.Contracts; 6 using OpticalTrap.Web.Facade.Controllers; 7 using OpticalTrap.Web.Models; 8 using System.Reflection; 9 using System.Security.Claims; 10 using System.Text; 11 12 namespace OpticalTrap.Web.Facade.Extensions.Filters 13 { 14 /// <summary> 15 /// 該類型定義了全局處理操作日誌的過濾器,該類型是密封類型。 16 /// </summary> 17 public sealed class GlobalOperationLogFilterAttribute : Attribute, IActionFilter, IAsyncActionFilter 18 { 19 #region 實例欄位 20 21 private readonly ILogger<GlobalOperationLogFilterAttribute> _logger; 22 private readonly IServiceProvider _serviceProvider; 23 24 #endregion 25 26 #region 構造函數 27 28 /// <summary> 29 /// 初始化該類型的新實例。 30 /// </summary> 31 /// <param name="logger">需要註入的日誌服務實例。</param> 32 /// <param name="serviceProvider">需要註入的服務提供器。</param> 33 public GlobalOperationLogFilterAttribute(ILogger<GlobalOperationLogFilterAttribute> logger, IServiceProvider serviceProvider) 34 { 35 _logger = logger; 36 _serviceProvider = serviceProvider; 37 } 38 39 #endregion 40 41 #region 操作日誌的同步方法 42 43 /// <summary> 44 /// 在標註方法執行之前執行該方法。 45 /// </summary> 46 /// <param name="context">方法執行前的上下文。</param> 47 public async void OnActionExecuting(ActionExecutingContext context) 48 { 49 if (context.Controller.GetType() != typeof(ErrorHandlerController)) 50 { 51 if (context.ActionDescriptor.EndpointMetadata.Any(c => c.GetType() == typeof(RequiredLogAttribute))) 52 { 53 #region 核心處理 54 55 var controllerType = context.Controller.GetType(); 56 var currentMethodName = context.ActionDescriptor.RouteValues["action"]!; 57 58 string? loginName = string.Empty; 59 var claimKeysProvider = _serviceProvider.GetService<ClaimKeysConstProvider>(); 60 if (claimKeysProvider != null) 61 { 62 loginName = context.HttpContext.User.FindFirstValue(claimKeysProvider.ClaimStoreUserNameKey); 63 } 64 65 var currentDateTime = DateTime.Now; 66 var methodType = context.HttpContext.Request.Method; 67 string parameterResult = string.Empty; 68 if (string.Compare(methodType, "get", true) == 0) 69 { 70 if (!string.IsNullOrEmpty(context.HttpContext.Request.QueryString.Value) && !string.IsNullOrWhiteSpace(context.HttpContext.Request.QueryString.Value)) 71 { 72 parameterResult = context.HttpContext.Request.QueryString.Value; 73 } 74 } 75 else 76 { 77 //第三步:在同步方法里的使用:啟用倒帶, 讀取request.body里的的參數, 還必須在在Program.cs里也啟用倒帶功能 78 context.HttpContext.Request.EnableBuffering(); 79 context.HttpContext.Request.Body.Position = 0; 80 using (var memoryStream = new MemoryStream()) 81 { 82 context.HttpContext.Request.Body.CopyTo(memoryStream); 83 var streamBytes = memoryStream.ToArray(); 84 parameterResult = Encoding.UTF8.GetString(streamBytes); //把body賦值給bodyStr 85 } 86 //using (var reader = new StreamReader(context.HttpContext.Request.Body, Encoding.UTF8))
//{
// var bodyRead = reader.ReadToEndAsync();
// bodyStr = bodyRead.Result; //把body賦值給bodyStr
// needKey = JsonConvert.DeserializeAnonymousType
// (bodyRead.Result, new Dictionary<string, object>())[dependencySource].ToString();
//}
if (controllerType != typeof(ValidationController)) 87 { 88 parameterResult = ProcessFormParameters(parameterResult); 89 } 90 else 91 { 92 parameterResult = ProcessLoginUserNameParameters(parameterResult, controllerType, nameof(ValidationController), currentMethodName, out loginName); 93 } 94 } 95 96 parameterResult = !string.IsNullOrEmpty(parameterResult) ? parameterResult : "沒有傳遞任何參數"; 97 loginName = !string.IsNullOrEmpty(loginName) ? loginName : "anonymous"; 98 Guid userid = Guid.Empty; 99 if (string.IsNullOrEmpty(context.HttpContext.User.FindFirstValue(ClaimTypes.Sid)) || string.IsNullOrWhiteSpace(context.HttpContext.User.FindFirstValue(ClaimTypes.Sid))) 100 { 101 userid = Guid.Parse("a05897f9-0c86-4f5a-a581-e5da936d0e4c"); 102 } 103 else 104 { 105 userid = Guid.Parse(context.HttpContext.User.FindFirstValue(ClaimTypes.Sid)); 106 } 107 108 OperationLog log = new OperationLog() 109 { 110 Id = Guid.NewGuid(), 111 Name = $"{loginName}在{currentDateTime.ToString("yyyy-MM-dd HH:mm:ss")}執行了{currentMethodName}操作。", 112 LoginName = loginName, 113 Parameters = parameterResult, 114 ActionName = $"{controllerType.FullName}.{currentMethodName}", 115 ActionType = methodType, 116 Message = $"【{loginName}】用戶在【{currentDateTime.ToString("yyyy-MM-dd HH:mm:ss")}】執行了控制器【{controllerType.FullName}】的【{currentMethodName}】方法,執行方法的類型:{methodType},執行方法所需的參數:【{parameterResult}】,操作順利完成。", 117 Remarks = "全局日誌記錄器記錄的日誌。", 118 CreateUserId = userid, 119 CreateDate = currentDateTime 120 }; 121 122 try 123 { 124 MethodInfo? methodInfo; 125 if (controllerType.IsDefined(typeof(RequiredLogAttribute), false)) 126 { 127 methodInfo = controllerType.GetMethod(currentMethodName); 128 if (methodInfo != null) 129 { 130 _logger.LogInformation(JsonConvert.SerializeObject(log)); 131 } 132 } 133 else 134 { 135 methodInfo = controllerType.GetMethod(currentMethodName); 136 if (methodInfo != null) 137 { 138 if (methodInfo.IsDefined(typeof(RequiredLogAttribute), false)) 139 { 140 _logger.LogInformation(JsonConvert.SerializeObject(log)); 141 } 142 } 143 } 144 } 145 catch (Exception ex) 146 { 147 log.Name = $"異常操作日誌:{loginName}在{currentDateTime.ToString("yyyy-MM-dd HH:mm:ss")}執行了{currentMethodName}操作。"; 148 149 log.Message = $"【{loginName}】用戶在【{currentDateTime.ToString("yyyy-MM-dd HH:mm:ss")}】執行了控制器【{controllerType.FullName}】的【{currentMethodName}】方法,執行方法的類型:{methodType},執行方法所需的參數:【{parameterResult}】,操作沒有完成,系統發生了異常。<br/>異常詳情:{ex.Message},<br/>異常堆棧:{ex.StackTrace}。"; 150 151 _logger.LogInformation(JsonConvert.SerializeObject(log)); 152 } 153 154 #endregion 155 } 156 } 157 } 158 159 /// <summary> 160 /// 在標註方法執行之後執行該方法。 161 /// </summary> 162 /// <param name="context">方法執行後的上下文。</param> 163 public void OnActionExecuted(ActionExecutedContext context) { } 164 165 #endregion 166 167 #region 操作日誌的非同步方法 168 169 /// <summary> 170 /// 全局日誌記錄器非同步實現的操作日誌的記錄。 171 /// </summary> 172 /// <param name="context">方法執行前的上下文。</param> 173 /// <param name="next">方法執行的下一個環節代理。</param> 174 /// <returns></returns> 175 public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) 176 { 177 if (context.Controller.GetType() != typeof(ErrorHandlerController)) 178 { 179 if (context.ActionDescriptor.EndpointMetadata.Any(c => c.GetType() == typeof(RequiredLogAttribute))) 180 { 181 #region 核心處理 182 183 var controllerType = context.Controller.GetType(); 184 var currentMethodName = context.ActionDescriptor.RouteValues["action"]!; 185 186 string? loginName = string.Empty; 187 var claimKeysProvider = _serviceProvider.GetService<ClaimKeysConstProvider>(); 188 if (claimKeysProvider != null) 189 { 190 loginName = context.HttpContext.User.FindFirstValue(claimKeysProvider.ClaimStoreUserNameKey); 191 } 192 193 var currentDateTime = DateTime.Now; 194 var methodType = context.HttpContext.Request.Method; 195 string parameterResult = string.Empty; 196 if (string.Compare(methodType, "get", true) == 0) 197 { 198 if (!string.IsNullOrEmpty(context.HttpContext.Request.QueryString.Value) && !string.IsNullOrWhiteSpace(context.HttpContext.Request.QueryString.Value)) 199 { 200 parameterResult = context.HttpContext.Request.QueryString.Value; 201 } 202 } 203 else 204 { 205 //第三步:在非同步步方法里的使用:啟用倒帶, 讀取request.body里的的參數, 還必須在在Program.cs里也啟用倒帶功能 206 context.HttpContext.Request.EnableBuffering(); 207 context.HttpContext.Request.Body.Position = 0; 208 using (var memoryStream = new MemoryStream()) 209 { 210 context.HttpContext.Request.Body.CopyTo(memoryStream); 211 var streamBytes = memoryStream.ToArray(); 212 parameterResult = Encoding.UTF8.GetString(streamBytes); //把body賦值給bodyStr 213 }
//using (var reader = new StreamReader(context.HttpContext.Request.Body, Encoding.UTF8))
//{
// var bodyRead = reader.ReadToEndAsync();
// bodyStr = bodyRead.Result; //把body賦值給bodyStr
// needKey = JsonConvert.DeserializeAnonymousType
// (bodyRead.Result, new Dictionary<string, object>())[dependencySource].ToString();
//} 214 if (controllerType != typeof(ValidationController)) 215 { 216 parameterResult = ProcessFormParameters(parameterResult); 217 } 218 else 219 { 220 parameterResult = ProcessLoginUserNameParameters(parameterResult, controllerType, nameof(ValidationController), currentMethodName, out loginName); 221 } 222 } 223 224 parameterResult = !string.IsNullOrEmpty(parameterResult) ? parameterResult : "沒有傳遞任何參數"; 225 loginName = !string.IsNullOrEmpty(loginName) ? loginName : "anonymous"; 226 Guid userid = Guid.Empty; 227 if (string.IsNullOrEmpty(context.HttpContext.User.FindFirstValue(ClaimTypes.Sid)) || string.IsNullOrWhiteSpace(context.HttpContext.User.FindFirstValue(ClaimTypes.Sid))) 228 { 229 userid = Guid.Parse("a05897f9-0c86-4f5a-a581-e5da936d0e4c"); 230 } 231 else 232 { 233 userid = Guid.Parse(context.HttpContext.User.FindFirstValue(ClaimTypes.Sid)); 234 } 235 236 OperationLog log = new OperationLog() 237 { 238 Id = Guid.NewGuid(), 239 Name = $"{loginName}在{currentDateTime.ToString("yyyy-MM-dd HH:mm:ss")}執行了{currentMethodName}操作。", 240 LoginName = loginName, 241 Parameters = parameterResult, 242 ActionName = $"{controllerType.FullName}.{currentMethodName}", 243 ActionType = methodType, 244 Message = $"【{loginName}】用戶在【{currentDateTime.ToString("yyyy-MM-dd HH:mm:ss")}】執行了控制器【{controllerType.FullName}】的【{currentMethodName}】方法,執行方法的類型:{methodType},執行方法所需的參數:【{parameterResult}】,操作順利完成。", 245 Remarks = "全局日誌記錄器記錄的日誌。", 246 CreateUserId = userid, 247 CreateDate = currentDateTime 248 }; 249 250 try 251 { 252 MethodInfo? methodInfo; 253 if (controllerType.IsDefined(typeof(RequiredLogAttribute), false)) 254 { 255 methodInfo = controllerType.GetMethod(currentMethodName); 256 if (methodInfo != null) 257 { 258 _logger.LogInformation(JsonConvert.SerializeObject(log)); 259 } 260 } 261 else 262 { 263 methodInfo = controllerType.GetMethod(currentMethodName); 264 if (methodInfo != null) 265 { 266 if (methodInfo.IsDefined(typeof(RequiredLogAttribute), false)) 267 { 268 _logger.LogInformation(JsonConvert.SerializeObject(log)); 269 } 270 } 271 } 272 } 273 catch (Exception ex) 274 { 275 log.Name = $"異常操作日誌:{loginName}在{currentDateTime.ToStr