一、簡介
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();
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.ToString("yyyy-MM-dd HH:mm:ss")}執行了{currentMethodName}操作。"; 276 log.Message = $"【{loginName}】使用者在【{currentDateTime.ToString("yyyy-MM-dd HH:mm:ss")}】執行了控制器【{controllerType.FullName}】的【{currentMethodName}】方法,執行方法的型別:{methodType},執行方法所需的引數:【{parameterResult}】,操作沒有完成,系統發生了異常。<br/>異常詳情:{ex.Message},<br/>異常堆疊:{ex.StackTrace}。"; 277 278 _logger.LogInformation(JsonConvert.SerializeObject(log)); 279 } 280 281 #endregion 282 } 283 } 284 285 await next.Invoke(); 286 } 287 288 /// <summary> 289 /// 處理引數。 290 /// </summary> 291 /// <param name="parameters">要處理的引數字串。</param> 292 /// <param name="controllerType">控制器的型別。</param> 293 /// <param name="controllerFullName">控制器的全名。</param> 294 /// <param name="currentMethodName">當前呼叫的方法名稱。</param> 295 /// <param name="loginName">登入系統的使用者名稱稱。</param> 296 /// <returns></returns> 297 private string ProcessLoginUserNameParameters(string parameters, Type controllerType, string controllerFullName, string currentMethodName, out string loginName) 298 { 299 loginName = string.Empty; 300 if (parameters.IndexOf("&__RequestVerificationToken") != -1) 301 { 302 parameters = parameters.Substring(0, parameters.LastIndexOf("&__RequestVerificationToken")); 303 if ((string.Compare(controllerType.FullName, controllerFullName, true) == 0) && (string.Compare(currentMethodName, "login", true) == 0)) 304 { 305 if (parameters.IndexOf("userName=") != -1) 306 { 307 loginName = parameters.Substring("userName=".Length, parameters.IndexOf("&password") - "&password".Length); 308 parameters = $"{parameters.Substring(0, parameters.LastIndexOf("=") + 1)}*********"; 309 } 310 } 311 } 312 else 313 { 314 if ((string.Compare(controllerType.FullName, controllerFullName, true) == 0) && (string.Compare(currentMethodName, "login", true) == 0)) 315 { 316 if (parameters.IndexOf("userName=") != -1) 317 { 318 loginName = parameters.Substring("userName=".Length, parameters.IndexOf("&password") - "&password".Length); 319 parameters = $"{parameters.Substring(0, parameters.LastIndexOf("=") + 1)}*********"; 320 } 321 } 322 } 323 return parameters; 324 } 325 326 /// <summary> 327 /// 返回經過處理的 Form 表單引數。 328 /// </summary> 329 /// <param name="originalFormParameters">未經處理的、原始的 Form 表單引數。</param> 330 /// <returns></returns> 331 private string ProcessFormParameters(string originalFormParameters) 332 { 333 string result = "沒有 Form 表單引數。"; 334 if (string.IsNullOrEmpty(originalFormParameters) || string.IsNullOrWhiteSpace(originalFormParameters)) 335 { 336 return result; 337 } 338 339 if (originalFormParameters.IndexOf("=") != -1 && (originalFormParameters.IndexOf("=") != originalFormParameters.LastIndexOf("="))) 340 { 341 var formParameters = originalFormParameters.Split(new string[] { "-----------------------------", "Content-Disposition: form-data;" }, StringSplitOptions.RemoveEmptyEntries); 342 var filterParameter = new List<string>(); 343 344 //獲取引數資料,包含=等號的就是form表單的值 345 foreach (var parameter in formParameters) 346 { 347 if (parameter.IndexOf("=") != -1 && parameter.IndexOf("__RequestVerificationToken", StringComparison.CurrentCultureIgnoreCase) == -1) 348 { 349 filterParameter.Add(parameter); 350 } 351 } 352 //整理表單資料格式為:name='xxxx' value='yyyyyy'\r\nname='xxxx2' value='yyyyyy2'.... 353 if (filterParameter.Count > 0) 354 { 355 for (int i = 0; i < filterParameter.Count; i++) 356 { 357 filterParameter[i] = ProcessCore(filterParameter[i]); 358 } 359 } 360 361 //憑藉結果值,並返回。 362 if (filterParameter.Count > 0) 363 { 364 result = string.Join("\r\n", filterParameter); 365 } 366 } 367 368 return result; 369 } 370 371 /// <summary> 372 /// 遞迴的處理引數的格式,將格式轉換為 name='xxxx' value='yyyyyy'。 373 /// </summary> 374 /// <param name="parameter">要處理的引數。</param> 375 /// <returns>返回處理好的引數。</returns> 376 private string ProcessCore(string parameter) 377 { 378 //過濾Form表單中的圖片,只獲取欄位名和值,具體上傳檔案的資料不保留。 379 if (parameter.IndexOf("Content-Type: image/", StringComparison.CurrentCultureIgnoreCase) != -1) 380 { 381 parameter = parameter.Substring(0, parameter.IndexOf("Content-Type: image/")); 382 } 383 else if (parameter.IndexOf("\"") != -1)//替換資料中的斜槓和雙引號為單引號 384 { 385 parameter = parameter.Replace("\"", "'"); 386 } 387 else if (parameter.IndexOf("\r\n\r\n") != -1) 388 //替換資料中的兩個換行符為value=',格式:「name=\"Details\"\r\n\r\n<p>asdfsadfas</p><p>asdfsadf</p><p>fasdfsadfsadf</p>\r\n」== 「name=\"Details\" value='<p>asdfsadfas</p><p>asdfsadf</p><p>fasdfsadfsadf</p>'」 389 { 390 parameter = parameter.Replace("\r\n\r\n", " value='"); 391 } 392 else if (parameter.EndsWith("\r\n")) 393 //替換資料尾部的換行符為單引號,格式:「name=\"Details\"\r\n\r\n<p>asdfsadfas</p><p>asdfsadf</p><p>fasdfsadfsadf</p>\r\n」== 「name=\"Details\"\r\n\r\n<p>asdfsadfas</p><p>asdfsadf</p><p>fasdfsadfsadf</p>'」 394 { 395 parameter = parameter.Replace("\r\n", "'"); 396 } 397 else if (parameter.IndexOf(";") != -1) 398 { 399 parameter = parameter.Replace(";", " "); 400 } 401 else if (parameter.IndexOf("''") != -1) 402 { 403 parameter = parameter.Replace("''", "'"); 404 } 405 else 406 { 407 return parameter; 408 } 409 return ProcessCore(parameter); 410 } 411 412 #endregion 413 } 414 }