一、简介
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 }