Asp.Net Ending Response options, Response.End() vs CompleteRequest()

this blog is for any1 who get the ThreadAbortException when caling Response.End(), and then tried the other options such as HttpContext.Current.ApplicationInstance.CompleteRequest() and found out that there was no replacement.

i'll start with the bottom line: if you want to stop right now your response and send it as is you dont have any other option other than Response.End(), period and it's the right thing to throw that EX in that case.

the answer is its because there is the "HTTP pipeline" and the "Asp.Net pipeline", and CompleteRequest() cuts the HTTP pipeline but not the Asp.Net one so everything that is from handlers forwars (including all page events) continues but your part, the asp one, stays in your hands so that no information will be lost, so ur suppose to manage your code currectly, since information lost is really a bad thing, although in most cases we use Response.End() we know what we are losing and we want it.

see this short article about the why not:
http://weblogs.asp.net/hajan/why-not-to-use-httpresponse-close-and-httpresponse-end

so MSDN advice is to implemet flags like this

from MSDN "Causes ASP.NET to bypass all events and filtering in the HTTP pipeline chain of execution and directly execute the EndRequest event."
the best image i could find is this one where all the part in the "Http Modules" is the http pipeline and from there there is everything we know.
http://www.west-wind.com/presentations/howaspnetworks/Figure%206.png

now lets start with my little case story, which is relevant since i created a handler dedicated to ajax based forms, and i saw that in all my T/C i have similar logic so i decided to create a smart enough exception class of my own that will logically do the following
1. Send to the client a "client error message"
2. Log everything
3. End the response, not allowing the code to continue

in my handler every T/C used only my custom exception to forward the EX, and just in case i added a T/C on the entire ProcessRequest method, and in the catch i threw again my custom EX.

so the 1st thing that happened was "System.ComponentModel.InvalidEnumArgumentException: The value of argument 'type' (0) is invalid for Enum type 'EventLogEntryType'.Parameter name: type"

this happened (i am writing this because there was no google for this EX) because of my poor implementation here:

public MyException(string message, string clientMessage, Exception ex)
: base(message, ex)
{
    ClientMessage = clientMessage;
    WriteErrorToLogAndFlush();
   
EntryType = EventLogEntryType.Error;
}


void WriteErrorToLogAndFlush()
{
  SPSecurity.RunWithElevatedPrivileges(() =>{   
    string sSource = "MyException";
    string sEvent = "MyException: " + this.ToString();
 
    EventLog.WriteEntry(sSource, sEvent, EntryType);

    if (HttpContext.Current != null) {
        HttpContext.Current.Response.ContentType = "text/plain";
        HttpContext.Current.Response.StatusCode = 500;
        HttpContext.Current.Response.Write(ClientMessage);

        HttpContext.Current.Response.End();
    }
 
   else {
   
    EventLog.WriteEntry(sSource, "MyException.WriteErrorToLogAndFlush: HttpContext.Current is null", EventLogEntryType.Error);
    }
  });
}
 


so as you can see, I called EntryType Before it was declared.

next was the infamous ThreadAbortException, caused by Response.End()

honestly, even after almost 3 years of .NETting and dozens of Response.End()s (and Response.Redirect()) i never noticed that it throws that EX, so in the beginning i was pretty worried, and everywhere (including MSDN and STACKOVERFLOW) i was promted by everybody to use HttpContext.Current.ApplicationInstance.CompleteRequest(), which we will call form now just CompleteRequest(), so i switched and nothing happed, its like i never did it.

lets remeber my scenario in order to understand, what i wanted to achieve is to create a handler and in any case of throwing an exception I wanted the code to stop and return a different response with a basic message to the client while logging the error.
so to mimic that here is a basic code:

public class h1 : IHttpHandler
{
   public bool IsReusable { get { return false; } }
   public string outputer { get; set; }
   public void ProcessRequest(HttpContext context)
   {
 
     //this is just to see the difference between server and client flow in the evenviewer
      outputer = "LogEntry ";
      try {
 
     //here was all my code and methods calls
          try {        
                 context.Response.Write(" JSON ");
              throw new Exception("an OOTB EX or a custom of my own");
          }
          catch (Exception ex) {
              throw new EX2(" HI ", "info", ex);
              HttpContext.Current.Response.Write(" EVIL");
              outputer += "EVIL ";
          }
      }
      catch (EX2 ex) {
 
         string got = " OK ";
          outputer += "OK ";
          HttpContext.Current.Response.Write(got);
      }
      catch (Exception ex) {
          string got = " BAD ";
          outputer += "BAD ";
          HttpContext.Current.Response.Write(got);
      }
      HttpContext.Current.Response.Write(" TREVIL");
      outputer += "TREVIL ";
      EventLog.WriteEntry("response ending testings", outputer, EventLogEntryType.Information);
   }
}
class EX2 : Exception
{
   public EX2(string clientMessage, string message, Exception ex) : base(message, ex)
   {
       WriteErrorToLogAndFlush(clientMessage);
   }
   void WriteErrorToLogAndFlush(string clientMessage)
   {

       HttpContext.Current.Response.Clear();       
       HttpContext.Current.Response.ContentType = "text/plain";
       HttpContext.Current.Response.StatusCode = 500;
       HttpContext.Current.Response.Write(clientMessage);
       //lets stop everything here so only HI will be sent
       //code line to stop response
   }
}


so lets see our options:
1. no extra code - the client gets "HI OK TREVIL", meaning that i clear the response, but the response bubbled to the main T/C, and continued to the code after it. in the eventviewer i get "OK TREVIL", where i want to see nothing at all.
Conclustion: i could always let the error bubble and check for EX2 and skip hadling (double catch).
in a Page: also require flags.

2. Response.End()  - the server is good, since no event is getting logged, but my client gets "HI BAD", and while debugging i saw that this is because the ThreadAbortException was thrown and the main catch cought it.
Conclusion: if i accept using a ThreadAbortExeption handling (double catch again) i get just what i wanted although its considered bad practice.
in a Page: same.

3. HttpContext.Current.ApplicationInstance.CompleteRequest() -  so i googled to get promted to use CompleteRequest(), there i get again "HI OK TREVIL" and "OK TREVIL", so there is no meaning!?!
so after some googling i found this bit more detailed stack
http://stackoverflow.com/questions/1087777/is-response-end-considered-harmful
where Jay Zelos informs about Response.SuppressContent = True
so i replaced my code line to
Response.Flush();
Response.SuppressContent = true;
HttpContext.Current.ApplicationInstance.CompleteRequest();
the result in the client was great, only "HI", just what i wanted. i debugged for safety to find out that the code was executing as usual to my unhappy suprise, and in my sample code i got in the events "OK TREVIL", meaning that the EX is bubling and the rest of the code is executing.
BTW removing CompleteRequest() give the same results.

Conclusion: - Flush() + SuppressContent does the job client-side-wise, and leave me to handle my code. with a double catch for my EX2 i'm preety good.
in a Page: same (must be used with Response.Write())
Second Conclusion: seems that CompleteRequest() for us currently dont do much more that performance...unless you have something in modules that anoys you.

optimally in my case was using both SuppressContent and Response.End() with a double catch for ThreadAbortException.

Undestanding the difference between Response.End() and CompleteRequest():
the theroey is simple. every request to the IIS is initially wrapped in an HttpApplication object, that has a set of events that when fired can be implemented in HttpModules, there you are still in the context of HttpApplication as a sender, although you still have as a property your HttpContext.

but then each request find its handler (only 1), and from there on your context is HttpContext object as a parameter. from that point there is a hole new chain of events and when they end the request returns to the upper chain of event. you cannot modify anymore the HttpApplication, only access it.

CompleteRequest() says that the handler/page finished the job and the modules dont need to continue their jobs. it trusts the coder to handle everything inside.
Response.End() is a knife cutting everything, it kill the thread, and as MSDN states it was made for a time and scopes with no better solutions.

and if i want to implement a "safe" Response.End()
marry for us many people did a reflection on Response.End() and here is how its implemented (taken from the stack Q):
public void End()
{
    if (this._context.IsInCancellablePeriod)
    {
        InternalSecurityPermissions.ControlThread.Assert();
        Thread.CurrentThread.Abort(new HttpApplication.CancelModuleException(false));
    }
    else if (!this._flushing)
    {
        this.Flush();
        this._ended = true;
        if (this._context.ApplicationInstance != null)
        {
            this._context.ApplicationInstance.CompleteRequest();
        }
    }
}
we can see 2 very interesting things here.
the second is that if the context is not cancellable and the response is not yet flushing it implements just what i did in my handler, flush, i assume that this._ended is somewhere equals to SuppressContent, and if its inside an HttpApplication, CompleteRequest(), the "recommended" behavior.

what shines for me is that it just aborting the thread as is, so i tried that, i just replaced my line with Thread.CurrentThread.Abort(). the results was devastating, the IIS pretty much stopped working, many errors were logged, and no response at all in the client nor server (with the IISExpress he was closed).

i looked for this HttpApplication.CancelModuleException but its a sealed class, not for humans. MS has their own way to kill a thread without killing the IIS, and they are not sharing (wrapping in a T/C didnt help).


bottom line is that ther is no replacement for Response.End().
a right code pre-handles a case for stopping the standard execution, similar to the link above and can help itself with Flush() + SuppressContent + RequestComplete().
a short code uses the Response.End() with T/C.

for deeper understanding read this great presentation
http://www.west-wind.com/presentations/howaspnetworks/howaspnetworks.asp

Comments

Post a Comment

Popular posts from this blog

OverTheWire[.com] Natas Walkthrough - JUST HINT, NO SPOILERS

SOLVED The item could not be indexed successfully because the item failed in the indexing subsystem