0

I've build a very simple logging middleware that I use to get request and response body. Everything works just fine if I return IHttpActionResult or Task<IHttpActionResult> from my methods, but when I change them to HttpResponseMessage or Task<HttpResponseMessage> I get System.AccessViolationException

I've created simple controller that shows my issue:

[RoutePrefix("test")]
public class TestController : BaseApiController
{
    [AllowAnonymous]
    [HttpGet]
    [Route("a")]
    public IHttpActionResult A()
    {
        return Ok("A");
    }

    [AllowAnonymous]
    [HttpGet]
    [Route("b")]
    public async Task<IHttpActionResult> B()
    {
        //some long running operation
        await Task.Delay(1);
        return Ok("B");
    }

    [AllowAnonymous]
    [HttpGet]
    [Route("c")]
    public HttpResponseMessage C()
    {
        const string xml = "<response><body>C</body></response>";
        return new HttpResponseMessage()
        {
            Content = new StringContent(xml, Encoding.UTF8, "application/xml")
        };
    }

    [AllowAnonymous]
    [HttpGet]
    [Route("d")]
    public async Task<HttpResponseMessage> D()
    {
        //some long running operation
        await Task.CompletedTask;
        const string xml = "<response><body>D</body></response>";
        return new HttpResponseMessage()
        {
            Content = new StringContent(xml, Encoding.UTF8, "application/xml")
        };
    }
}

and my Middleware:

public static class RequestLoggerMiddlewareExtensions
{
    public static void UseRequestLogger(this IAppBuilder app)
    {
        app.Use<RequestLoggerMiddleware>();
    }
}

internal class RequestLoggerMiddleware : OwinMiddleware
{
    public RequestLoggerMiddleware(OwinMiddleware next) : base(next)
    {
    }

    public override async Task Invoke(IOwinContext context)
    {
        Stream oldResponseBody;
        string ip = context.Request.RemoteIpAddress;

        var reqContentType = context.Request.ContentType;

        //get the response body and put it back for the downstream items to read
        var reqHeaders = string.Join(",",context.Request.Headers.Select(h=>"["+h.Key+"]"+h.Value[0]).ToArray());
        string reqBody = new StreamReader(context.Request.Body).ReadToEnd();
        byte[] requestData = Encoding.UTF8.GetBytes(reqBody);
        context.Request.Body = new MemoryStream(requestData);

        var queryString = context.Request.QueryString.ToString();

        //read the path and the request method
        string reqPath = context.Request.Path.ToUriComponent();
        string reqMethod = context.Request.Method;

        //buffer the response stream
        oldResponseBody = context.Response.Body;
        //create a memory stream replacement
        MemoryStream replacement = new MemoryStream();
        //assing the replacement to the resonse body
        context.Response.Body = replacement;

        //Do the next thing
        await Next.Invoke(context);

        //grab the response code
        int responseCode = context.Response.StatusCode;

        //grab the response stream buffer
        //move to the beginning
        //copy it to the old response stream 
        replacement.Seek(0, SeekOrigin.Begin);

        //**********************Put the response back on the request body - this sends it down stream to the caller!
        await replacement.CopyToAsync(oldResponseBody);

        //move back to the head of the memory stream
        replacement.Seek(0, SeekOrigin.Begin);
        //read the response for logging
        StreamReader sr = new StreamReader(replacement);
        string responseBody = sr.ReadToEnd();

        //grab the response phrase
        string responseReasonPhrase = context.Response.ReasonPhrase;
        var responseContentType = context.Response.ContentType;

        Debug.WriteLine("IP:{0}, REQ:{1},REQ_PATH:{2},REQ_METHOD:{3},REQ_CONTENT_TYPE:{4},RES_STATUS:{5},RES_REASON:{6},RES_CONTENT_TYPE:{7},RES_BODY:{8}", ip, reqBody, reqPath, reqMethod, reqContentType, responseCode, responseReasonPhrase, responseContentType, responseBody);
    }
}

I'm adding this middleware inside Startup.cs in Configuration method adding app.UseRequestLogger();

Exception shows inside Visual Studio, so I can't even debug ad check what is happening.

What can be the cause of that error? Any clues are welcome.

Misiu
  • 4,738
  • 21
  • 94
  • 198
  • 1
    http://stackoverflow.com/questions/38275886/c-modifying-owin-response-stream-causes-accessviolationexception – Nathan Oct 19 '16 at 18:02
  • @Nathan thanks for link, I wasn't sure this would help, but it did. Thanks! Do You know why this is happening? I'd like to have everything async, but this `bug` is so annoying. Also please post answer to my question, I'll be happy to accept it. – Misiu Oct 20 '16 at 19:48
  • I don't think this is a bug though. Maybe a by design. Because even it's an await call there's no way to check if oldResponseBody is not null. AccessViolationException there is rather a wrapped exception of NullReferenceException to me. I'll leave it here since I'm not the first one to answer it and it's not an original one. Thanks. – Nathan Oct 20 '16 at 20:20
  • Ofc another theory is the oldResponseBody stream somehow was closed before await was actually executed, thus an AccessViolationException was thrown. – Nathan Oct 20 '16 at 20:26
  • Maybe wrap oldResponseBody into a using statement would also solve this. Just guessing. – Nathan Oct 20 '16 at 20:28
  • @Nathan thanks for hint, I'll try that. WHen I remove async everything works, so problem solved, but I'm curious why this is working with IHttpActionResult and not with HttpResponseMessage. – Misiu Oct 20 '16 at 20:39

0 Answers0