C#:修改Owin响应流会导致AccessViolationException

我试图在特定情况下使用一些自定义Owin中间件来修改(在这种情况下,完全替换)响应流。

每当我打电话确实触发我的中间件来替换响应时,一切正常。 只有当我打电话给我的中间件没有进行更改时,才会出现此问题。 此外,我只能在未被替换的API调用返回手动创建的HttpResponseMessage对象时发生错误。

例如,调用此API:

public class testController : ApiController { public HttpResponseMessage Get() { return Request.CreateResponse(HttpStatusCode.OK,new { message = "It worked." }); } } 

工作正常,但这个类:

 public class testController : ApiController { public HttpResponseMessage Get() { HttpResponseMessage m = Request.CreateResponse(); m.StatusCode = HttpStatusCode.OK; m.Content = new StringContent("It worked.", System.Text.Encoding.UTF8, "text/plain"); return m; } } 

导致错误发生。 (在这两种情况下,都在调用http://localhost:/test 。)

该错误导致以下任一情况:

  • 导致iisexpress.exe(如果在实际IIS中运行,则为w3wp.exe)因访问冲突而崩溃。
  • 抛出Visual Studio捕获的AccessViolationException ,但无法在外部代码中执行任何操作。 当Visual Studio确实捕获exception时,我看到:

     An unhandled exception of type 'System.AccessViolationException' occurred in System.Web.dll Additional information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt. 

显然,如果我不启用我的中间件,我根本就没有问题。 此外,我只能在手动创建和返回HttpResponseMessage对象时发生问题,如第二个类所示。

这是我的中间件类。 它的当前设置只是在有人请求端点/replace时简单地替换整个响应流,无论管道中的其他任何东西是否对它做了什么。

 using Microsoft.Owin; using Owin; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Net; using System.Threading.Tasks; using Newtonsoft.Json; using AppFunc = System.Func< System.Collections.Generic.IDictionary, System.Threading.Tasks.Task >; namespace TestOwinAPI { public class ResponseChangeMiddleware { AppFunc _next; public ResponseChangeMiddleware(AppFunc next, ResponseChangeMiddlewareOptions opts) { _next = next; } public async Task Invoke(IDictionary env) { var ctx = new OwinContext(env); // create a new memory stream which will replace the default output stream using (var ms = new MemoryStream()) { // hold on to a reference to the actual output stream for later use var outStream = ctx.Response.Body; // reassign the context's output stream to be our memory stream ctx.Response.Body = ms; Debug.WriteLine(" <- " + ctx.Request.Path); // allow the rest of the middleware to do its job await _next(env); // Now the request is on the way out. if (ctx.Request.Path.ToString() == "/replace") { // Now write new response. string json = JsonConvert.SerializeObject(new { response = "true", message = "This response will replace anything that the rest of the API might have created!" }); byte[] jsonBytes = System.Text.Encoding.UTF8.GetBytes(json); // clear everything else that anything might have put in the output stream ms.SetLength(0); // write the new data ms.Write(jsonBytes, 0, jsonBytes.Length); // set parameters on the response object ctx.Response.StatusCode = 200; ctx.Response.ContentLength = jsonBytes.Length; ctx.Response.ContentType = "application/json"; } // In all cases finally write the memory stream's contents back to the actual response stream ms.Seek(0, SeekOrigin.Begin); await ms.CopyToAsync(outStream); } } } public static class AppBuilderExtender { public static void UseResponseChangeMiddleware(this IAppBuilder app, ResponseChangeMiddlewareOptions options = null ) { if (options == null) options = new ResponseChangeMiddlewareOptions(); app.Use(options); } } public class ResponseChangeMiddlewareOptions { } } 

我做了很明显 – 一整夜的RAM测试(一切都很好),并尝试了另一个系统(它也发生在那里)。

此外,错误不一致 – 大约一半时间。 换句话说,通常我可以通过一两个成功的请求获得,但最终会发生错误。

最后,如果我在我的中间件中的内存流复制之前在我的程序中放置一个断点,并慢慢地逐步执行代码,则错误永远不会发生。 这向我表明我必须遇到某种竞争条件,而且必须与我正在玩MemoryStreams这一事实有关。

有任何想法吗?

天啊。

我不确定改变这是否是正确的做法,但它肯定解决了这个问题:

 await ms.CopyToAsync(outStream); 

 ms.CopyTo(outStream); 

我的唯一猜测是,在异步调用完成复制之前,应用程序正在关闭MemoryStream,这是有道理的。