Last active
July 7, 2017 15:48
-
-
Save jmserrano-dev/a94f5f747865603c5e08fb5f9fed41f5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 27d8be0c7e50b84e6f8b49236ca00ecc32df490b Mon Sep 17 00:00:00 2001 | |
From: OlegBelousov <OlegBelousov@mail.ru> | |
Date: Wed, 25 May 2016 17:55:07 +0300 | |
Subject: [PATCH] Synchronous execution of asynchronous web api stack. | |
It covers including the following questions: | |
http://stackoverflow.com/questions/33026799/mono-net-support-for-async-await | |
http://stackoverflow.com/questions/37012143/mono-with-owin-authentication | |
http://stackoverflow.com/questions/34834346/404-response-appended-to-webapi-response | |
http://stackoverflow.com/questions/34629192/mono-the-view-index-or-its-master-was-not-found | |
--- | |
System.Web/System.Web/HttpApplication.cs | 346 +++++++++++++-------- | |
.../System.Web/System.Web/HttpContextWrapper.cs | 11 + | |
System.Web/System.Web/HttpResponse.cs | 7 + | |
.../System.Web/System.Web/HttpResponseWrapper.cs | 3 + | |
4 files changed, 231 insertions(+), 136 deletions(-) | |
diff --git a/System.Web/System.Web/HttpApplication.cs b/System.Web/System.Web/HttpApplication.cs | |
index c2fc0ddb71ae..84a6fcf1461c 100644 | |
--- a/System.Web/System.Web/HttpApplication.cs | |
+++ b/System.Web/System.Web/HttpApplication.cs | |
@@ -146,9 +146,6 @@ public class HttpApplication : IHttpAsyncHandler, IHttpHandler, IComponent, IDis | |
// The current IAsyncResult for the running async request handler in the pipeline | |
AsyncRequestState begin_iar; | |
- // Tracks the current AsyncInvocation being dispatched | |
- AsyncInvoker current_ai; | |
- | |
EventHandlerList events; | |
EventHandlerList nonApplicationEvents = new EventHandlerList (); | |
@@ -167,14 +164,6 @@ public class HttpApplication : IHttpAsyncHandler, IHttpHandler, IComponent, IDis | |
static DynamicModuleManager dynamicModuleManeger = new DynamicModuleManager (); | |
- // | |
- // These are used to detect the case where the EndXXX method is invoked | |
- // from within the BeginXXXX delegate, so we detect whether we kick the | |
- // pipeline from here, or from the the RunHook routine | |
- // | |
- bool must_yield; | |
- bool in_begin; | |
- | |
public virtual event EventHandler Disposed { | |
add { nonApplicationEvents.AddHandler (disposedEvent, value); } | |
remove { nonApplicationEvents.RemoveHandler (disposedEvent, value); } | |
@@ -919,15 +908,30 @@ internal void ProcessError (Exception e) | |
} | |
// | |
- // Ticks the clock: next step on the pipeline. | |
+ // Wait the end of Pipeline. | |
// | |
- internal void Tick () | |
+ internal void WaitPipeline () | |
{ | |
try { | |
- if (pipeline.MoveNext ()){ | |
- if ((bool)pipeline.Current) | |
- PipelineDone (); | |
+ while (pipeline.MoveNext ()){ | |
+ if (pipeline.Current == null) | |
+ break; | |
+ AsyncInvoker ai = pipeline.Current as AsyncInvoker; | |
+ if (ai != null) | |
+ { | |
+ if (!ai.IsCompleted) | |
+ ai.WaitOne(); | |
+ continue; | |
+ } | |
+ AsyncRequestInvoker ari = pipeline.Current as AsyncRequestInvoker; | |
+ if (ari != null) | |
+ { | |
+ if (!ari.IsCompleted) | |
+ ari.WaitOne (); | |
+ continue; | |
+ } | |
} | |
+ PipelineDone (); | |
} catch (ThreadAbortException taex) { | |
object obj = taex.ExceptionState; | |
Thread.ResetAbort (); | |
@@ -954,45 +958,7 @@ internal void Tick () | |
} | |
} | |
- void Resume () | |
- { | |
- if (in_begin) | |
- must_yield = false; | |
- else | |
- Tick (); | |
- } | |
- | |
- // | |
- // Invoked when our async callback called from RunHooks completes, | |
- // we restart the pipeline here. | |
- // | |
- void async_callback_completed_cb (IAsyncResult ar) | |
- { | |
- if (current_ai.end != null){ | |
- try { | |
- current_ai.end (ar); | |
- } catch (Exception e) { | |
- ProcessError (e); | |
- } | |
- } | |
- Resume (); | |
- } | |
- | |
- void async_handler_complete_cb (IAsyncResult ar) | |
- { | |
- IHttpAsyncHandler async_handler = ar != null ? ar.AsyncState as IHttpAsyncHandler : null; | |
- | |
- try { | |
- if (async_handler != null) | |
- async_handler.EndProcessRequest (ar); | |
- } catch (Exception e){ | |
- ProcessError (e); | |
- } | |
- | |
- Resume (); | |
- } | |
- | |
// | |
// This enumerator yields whether processing must be stopped: | |
// true: processing of the pipeline must be stopped | |
@@ -1004,15 +970,13 @@ IEnumerable RunHooks (Delegate list) | |
foreach (EventHandler d in delegates){ | |
if (d.Target != null && (d.Target is AsyncInvoker)){ | |
- current_ai = (AsyncInvoker) d.Target; | |
+ AsyncInvoker ai = (AsyncInvoker) d.Target; | |
try { | |
- must_yield = true; | |
- in_begin = true; | |
context.BeginTimeoutPossible (); | |
- current_ai.begin (this, EventArgs.Empty, async_callback_completed_cb, current_ai.data); | |
+ ai.Invoke(this, EventArgs.Empty); | |
+ yield return ai; | |
} finally { | |
- in_begin = false; | |
context.EndTimeoutPossible (); | |
} | |
@@ -1020,10 +984,8 @@ IEnumerable RunHooks (Delegate list) | |
// If things are still moving forward, yield this | |
// thread now | |
// | |
- if (must_yield) | |
- yield return stop_processing; | |
- else if (stop_processing) | |
- yield return true; | |
+ if (stop_processing) | |
+ yield return null; | |
} else { | |
try { | |
context.BeginTimeoutPossible (); | |
@@ -1032,7 +994,7 @@ IEnumerable RunHooks (Delegate list) | |
context.EndTimeoutPossible (); | |
} | |
if (stop_processing) | |
- yield return true; | |
+ yield return null; | |
} | |
} | |
} | |
@@ -1116,7 +1078,6 @@ void PipelineDone () | |
// context = null; -> moved to PostDone | |
pipeline = null; | |
- current_ai = null; | |
} | |
PostDone (); | |
@@ -1177,7 +1138,7 @@ IEnumerator Pipeline () | |
{ | |
Delegate eventHandler; | |
if (stop_processing) | |
- yield return true; | |
+ yield return null; | |
HttpRequest req = context.Request; | |
if (req != null) | |
req.Validate (); | |
@@ -1185,65 +1146,65 @@ IEnumerator Pipeline () | |
StartTimer ("BeginRequest"); | |
eventHandler = Events [BeginRequestEvent]; | |
if (eventHandler != null) { | |
- foreach (bool stop in RunHooks (eventHandler)) | |
- yield return stop; | |
+ foreach (var ar in RunHooks (eventHandler)) | |
+ yield return ar; | |
} | |
StopTimer (); | |
StartTimer ("AuthenticateRequest"); | |
eventHandler = Events [AuthenticateRequestEvent]; | |
if (eventHandler != null) | |
- foreach (bool stop in RunHooks (eventHandler)) | |
- yield return stop; | |
+ foreach (var ar in RunHooks (eventHandler)) | |
+ yield return ar; | |
StopTimer (); | |
StartTimer ("DefaultAuthentication"); | |
if (DefaultAuthentication != null) | |
- foreach (bool stop in RunHooks (DefaultAuthentication)) | |
- yield return stop; | |
+ foreach (var ar in RunHooks (DefaultAuthentication)) | |
+ yield return ar; | |
StopTimer (); | |
StartTimer ("PostAuthenticateRequest"); | |
eventHandler = Events [PostAuthenticateRequestEvent]; | |
if (eventHandler != null) | |
- foreach (bool stop in RunHooks (eventHandler)) | |
- yield return stop; | |
+ foreach (var ar in RunHooks (eventHandler)) | |
+ yield return ar; | |
StopTimer (); | |
StartTimer ("AuthorizeRequest"); | |
eventHandler = Events [AuthorizeRequestEvent]; | |
if (eventHandler != null) | |
- foreach (bool stop in RunHooks (eventHandler)) | |
- yield return stop; | |
+ foreach (var ar in RunHooks (eventHandler)) | |
+ yield return ar; | |
StopTimer (); | |
StartTimer ("PostAuthorizeRequest"); | |
eventHandler = Events [PostAuthorizeRequestEvent]; | |
if (eventHandler != null) | |
- foreach (bool stop in RunHooks (eventHandler)) | |
- yield return stop; | |
+ foreach (var ar in RunHooks (eventHandler)) | |
+ yield return ar; | |
StopTimer (); | |
StartTimer ("ResolveRequestCache"); | |
eventHandler = Events [ResolveRequestCacheEvent]; | |
if (eventHandler != null) | |
- foreach (bool stop in RunHooks (eventHandler)) | |
- yield return stop; | |
+ foreach (var ar in RunHooks (eventHandler)) | |
+ yield return ar; | |
StopTimer (); | |
StartTimer ("PostResolveRequestCache"); | |
eventHandler = Events [PostResolveRequestCacheEvent]; | |
if (eventHandler != null) | |
- foreach (bool stop in RunHooks (eventHandler)) | |
- yield return stop; | |
+ foreach (var ar in RunHooks (eventHandler)) | |
+ yield return ar; | |
StopTimer (); | |
StartTimer ("MapRequestHandler"); | |
// As per http://msdn2.microsoft.com/en-us/library/bb470252(VS.90).aspx | |
eventHandler = Events [MapRequestHandlerEvent]; | |
if (eventHandler != null) | |
- foreach (bool stop in RunHooks (eventHandler)) | |
- yield return stop; | |
+ foreach (var ar in RunHooks (eventHandler)) | |
+ yield return ar; | |
StopTimer (); | |
context.MapRequestHandlerDone = true; | |
@@ -1276,28 +1237,28 @@ IEnumerator Pipeline () | |
StopTimer (); | |
if (stop_processing) | |
- yield return true; | |
+ yield return null; | |
StartTimer ("PostMapRequestHandler"); | |
eventHandler = Events [PostMapRequestHandlerEvent]; | |
if (eventHandler != null) | |
- foreach (bool stop in RunHooks (eventHandler)) | |
- yield return stop; | |
+ foreach (var ar in RunHooks (eventHandler)) | |
+ yield return ar; | |
StopTimer (); | |
StartTimer ("AcquireRequestState"); | |
eventHandler = Events [AcquireRequestStateEvent]; | |
if (eventHandler != null){ | |
- foreach (bool stop in RunHooks (eventHandler)) | |
- yield return stop; | |
+ foreach (IAsyncResult ar in RunHooks (eventHandler)) | |
+ yield return ar; | |
} | |
StopTimer (); | |
StartTimer ("PostAcquireRequestState"); | |
eventHandler = Events [PostAcquireRequestStateEvent]; | |
if (eventHandler != null){ | |
- foreach (bool stop in RunHooks (eventHandler)) | |
- yield return stop; | |
+ foreach (var ar in RunHooks (eventHandler)) | |
+ yield return ar; | |
} | |
StopTimer (); | |
@@ -1309,11 +1270,12 @@ IEnumerator Pipeline () | |
StartTimer ("PreRequestHandlerExecute"); | |
eventHandler = Events [PreRequestHandlerExecuteEvent]; | |
if (eventHandler != null) | |
- foreach (bool stop in RunHooks (eventHandler)) | |
- if (stop) | |
+ foreach (var ar in RunHooks (eventHandler)) | |
+ if (ar == null) | |
goto release; | |
+ else | |
+ yield return ar; | |
StopTimer (); | |
- | |
IHttpHandler ctxHandler = context.Handler; | |
@@ -1328,13 +1290,11 @@ IEnumerator Pipeline () | |
context.BeginTimeoutPossible (); | |
if (handler != null){ | |
IHttpAsyncHandler async_handler = handler as IHttpAsyncHandler; | |
- | |
if (async_handler != null){ | |
- must_yield = true; | |
- in_begin = true; | |
- async_handler.BeginProcessRequest (context, async_handler_complete_cb, handler); | |
+ AsyncRequestInvoker ari = new AsyncRequestInvoker(async_handler, this); | |
+ ari.Invoke(context, handler); | |
+ yield return ari; | |
} else { | |
- must_yield = false; | |
handler.ProcessRequest (context); | |
} | |
} else | |
@@ -1342,13 +1302,10 @@ IEnumerator Pipeline () | |
if (context.Error != null) | |
throw new TargetInvocationException(context.Error); | |
} finally { | |
- in_begin = false; | |
context.EndTimeoutPossible (); | |
} | |
StopTimer (); | |
- if (must_yield) | |
- yield return stop_processing; | |
- else if (stop_processing) | |
+ if (stop_processing) | |
goto release; | |
// These are executed after the application has returned | |
@@ -1356,9 +1313,11 @@ IEnumerator Pipeline () | |
StartTimer ("PostRequestHandlerExecute"); | |
eventHandler = Events [PostRequestHandlerExecuteEvent]; | |
if (eventHandler != null) | |
- foreach (bool stop in RunHooks (eventHandler)) | |
- if (stop) | |
+ foreach (var ar in RunHooks (eventHandler)) | |
+ if (ar == null) | |
goto release; | |
+ else | |
+ yield return ar; | |
StopTimer (); | |
release: | |
@@ -1366,24 +1325,24 @@ IEnumerator Pipeline () | |
eventHandler = Events [ReleaseRequestStateEvent]; | |
if (eventHandler != null){ | |
#pragma warning disable 219 | |
- foreach (bool stop in RunHooks (eventHandler)) { | |
// | |
// Ignore the stop signal while release the state | |
// | |
- | |
- } | |
+ foreach (var ar in RunHooks (eventHandler)) | |
+ if (ar != null) | |
+ yield return ar; | |
#pragma warning restore 219 | |
} | |
StopTimer (); | |
if (stop_processing) | |
- yield return true; | |
+ yield return null; | |
StartTimer ("PostReleaseRequestState"); | |
eventHandler = Events [PostReleaseRequestStateEvent]; | |
if (eventHandler != null) | |
- foreach (bool stop in RunHooks (eventHandler)) | |
- yield return stop; | |
+ foreach (var ar in RunHooks (eventHandler)) | |
+ yield return ar; | |
StopTimer (); | |
StartTimer ("Filter"); | |
@@ -1394,33 +1353,29 @@ IEnumerator Pipeline () | |
StartTimer ("UpdateRequestCache"); | |
eventHandler = Events [UpdateRequestCacheEvent]; | |
if (eventHandler != null) | |
- foreach (bool stop in RunHooks (eventHandler)) | |
- yield return stop; | |
+ foreach (var ar in RunHooks (eventHandler)) | |
+ yield return ar; | |
StopTimer (); | |
StartTimer ("PostUpdateRequestCache"); | |
eventHandler = Events [PostUpdateRequestCacheEvent]; | |
if (eventHandler != null) | |
- foreach (bool stop in RunHooks (eventHandler)) | |
- yield return stop; | |
+ foreach (var ar in RunHooks (eventHandler)) | |
+ yield return ar; | |
StopTimer (); | |
StartTimer ("LogRequest"); | |
eventHandler = Events [LogRequestEvent]; | |
if (eventHandler != null) | |
- foreach (bool stop in RunHooks (eventHandler)) | |
- yield return stop; | |
+ foreach (var ar in RunHooks (eventHandler)) | |
+ yield return ar; | |
StopTimer (); | |
StartTimer ("PostLogRequest"); | |
eventHandler = Events [PostLogRequestEvent]; | |
if (eventHandler != null) | |
- foreach (bool stop in RunHooks (eventHandler)) | |
- yield return stop; | |
- StopTimer (); | |
- | |
- StartTimer ("PipelineDone"); | |
- PipelineDone (); | |
+ foreach (var ar in RunHooks (eventHandler)) | |
+ yield return ar; | |
StopTimer (); | |
} | |
@@ -1518,7 +1473,7 @@ void Start (object x) | |
HttpContext.Current = Context; | |
PreStart (); | |
pipeline = Pipeline (); | |
- Tick (); | |
+ WaitPipeline (); | |
} | |
const string HANDLER_CACHE = "@@HttpHandlerCache@@"; | |
@@ -1927,6 +1882,73 @@ internal void Complete () | |
} | |
#region Helper classes | |
+ | |
+ // | |
+ // A wrapper to keep track of begin/end pairs for request | |
+ // | |
+ class AsyncRequestInvoker { | |
+ IHttpAsyncHandler async_handler; | |
+ HttpApplication app; | |
+ AsyncCallback callback; | |
+ bool completed = false; | |
+ ManualResetEvent manual_event = new ManualResetEvent(false); | |
+ | |
+ CultureInfo _culture; | |
+ CultureInfo _ui_culture; | |
+ | |
+ public AsyncRequestInvoker (IHttpAsyncHandler ah, HttpApplication a) | |
+ { | |
+ app = a; | |
+ async_handler = ah; | |
+ callback = new AsyncCallback (doAsyncCallback); | |
+ } | |
+ | |
+ public void Invoke (HttpContext context, object extraData) | |
+ { | |
+ completed = false; | |
+ manual_event.Reset(); | |
+ | |
+ Thread th = Thread.CurrentThread; | |
+ _culture = th.CurrentCulture; | |
+ _ui_culture = th.CurrentUICulture; | |
+ | |
+ IAsyncResult res = async_handler.BeginProcessRequest (context, callback, extraData); | |
+ if (res.IsCompleted) | |
+ { | |
+ completed = true; | |
+ manual_event.Set(); | |
+ } | |
+ } | |
+ | |
+ public bool IsCompleted | |
+ { | |
+ get {return completed; } | |
+ } | |
+ | |
+ public void WaitOne() | |
+ { | |
+ manual_event.WaitOne (); | |
+ } | |
+ | |
+ void doAsyncCallback (IAsyncResult res) | |
+ { | |
+ Thread th = Thread.CurrentThread; | |
+ th.CurrentCulture = _culture; | |
+ th.CurrentUICulture = _ui_culture; | |
+ HttpContext.Current = app.Context; | |
+ | |
+ try { | |
+ if (res != null) | |
+ async_handler.EndProcessRequest(res); | |
+ } catch (Exception ee) { | |
+ app.ProcessError(ee); | |
+ } finally { | |
+ completed = true; | |
+ manual_event.Set(); | |
+ } | |
+ } | |
+ } | |
+ | |
// | |
// A wrapper to keep track of begin/end pairs | |
@@ -1937,6 +1959,12 @@ class AsyncInvoker { | |
public object data; | |
HttpApplication app; | |
AsyncCallback callback; | |
+ bool completed = false; | |
+ ManualResetEvent manual_event = new ManualResetEvent(false); | |
+ | |
+ CultureInfo _culture; | |
+ CultureInfo _ui_culture; | |
+ | |
public AsyncInvoker (BeginEventHandler bh, EndEventHandler eh, HttpApplication a, object d) | |
{ | |
@@ -1951,22 +1979,68 @@ public AsyncInvoker (BeginEventHandler bh, EndEventHandler eh, HttpApplication a | |
public void Invoke (object sender, EventArgs e) | |
{ | |
- IAsyncResult res; | |
- res = begin (app, e, callback, data); | |
+ completed = false; | |
+ manual_event.Reset(); | |
+ | |
+ Thread th = Thread.CurrentThread; | |
+ _culture = th.CurrentCulture; | |
+ _ui_culture = th.CurrentUICulture; | |
+ | |
+ IAsyncResult res = begin (app, e, callback, data); | |
+ if (res.IsCompleted) | |
+ { | |
+ completed = true; | |
+ manual_event.Set(); | |
+ } | |
+ } | |
+ | |
+ public bool IsCompleted | |
+ { | |
+ get {return completed; } | |
+ } | |
+ | |
+ public void WaitOne() | |
+ { | |
+ manual_event.WaitOne (); | |
} | |
void doAsyncCallback (IAsyncResult res) | |
{ | |
- ThreadPool.QueueUserWorkItem ((object ores) => { | |
- IAsyncResult tres = (IAsyncResult) ores; | |
- try { | |
- end (tres); | |
- } catch (Exception ee) { | |
- // I tried using ProcessError(), but we only come here frome an Invokation in PipelineDone(). | |
- // Using ProcessError, I still get a blank screen, this way, we at least log the error to console... | |
- Console.Error.WriteLine (ee.ToString ()); | |
- } | |
- }, res); | |
+ Thread th = Thread.CurrentThread; | |
+ th.CurrentCulture = _culture; | |
+ th.CurrentUICulture = _ui_culture; | |
+ HttpContext.Current = app.Context; | |
+ | |
+ try { | |
+ end (res); | |
+ } catch (Exception ee) { | |
+ // I tried using ProcessError(), but we only come here frome an Invokation in PipelineDone(). | |
+ // Using ProcessError, I still get a blank screen, this way, we at least log the error to console... | |
+ // Console.Error.WriteLine (ee.ToString ()); | |
+ | |
+ // Now, we may call ProcessError. | |
+ app.ProcessError(ee); | |
+ } finally { | |
+ completed = true; | |
+ manual_event.Set(); | |
+ } | |
+ | |
+// ThreadPool.QueueUserWorkItem ((object ores) => { | |
+// IAsyncResult tres = (IAsyncResult) ores; | |
+// try { | |
+// end (tres); | |
+// } catch (Exception ee) { | |
+// // I tried using ProcessError(), but we only come here frome an Invokation in PipelineDone(). | |
+// // Using ProcessError, I still get a blank screen, this way, we at least log the error to console... | |
+// // Console.Error.WriteLine (ee.ToString ()); | |
+// | |
+// // Now, we may call ProcessError. | |
+// app.ProcessError(ee); | |
+// } finally { | |
+// completed = true; | |
+// manual_event.Set(); | |
+// } | |
+// }, res); | |
} | |
} | |
#endregion | |
diff --git a/System.Web/System.Web/HttpContextWrapper.cs b/System.Web/System.Web/HttpContextWrapper.cs | |
index 64975a813c0f..b88ced527941 100644 | |
--- a/System.Web/System.Web/HttpContextWrapper.cs | |
+++ b/System.Web/System.Web/HttpContextWrapper.cs | |
@@ -212,5 +212,16 @@ public override void SetSessionStateBehavior (SessionStateBehavior sessionStateB | |
{ | |
w.SetSessionStateBehavior (sessionStateBehavior); | |
} | |
+ | |
+ internal static Action<HttpContext> WrapCallback(Action<HttpContextBase> callback) | |
+ { | |
+ if (callback != null) | |
+ { | |
+ return delegate (HttpContext context) { | |
+ callback(new HttpContextWrapper(context)); | |
+ }; | |
+ } | |
+ return null; | |
+ } | |
} | |
} | |
diff --git a/System.Web/System.Web/HttpResponse.cs b/System.Web/System.Web/HttpResponse.cs | |
index cc90a54ab971..82a2c0c47c3e 100644 | |
--- a/System.Web/System.Web/HttpResponse.cs | |
+++ b/System.Web/System.Web/HttpResponse.cs | |
@@ -644,6 +644,12 @@ public void End () | |
} | |
} | |
+ public ISubscriptionToken AddOnSendingHeaders(Action<HttpContext> callback) { | |
+ // TODO | |
+ // Console.Error.WriteLine("AddOnSendingHeaders"); | |
+ return null; | |
+ } | |
+ | |
// Generate: | |
// Content-Length | |
// Content-Type | |
@@ -730,6 +736,7 @@ void AddHeadersNoCache (NameValueCollection write_headers, bool final_flush) | |
} | |
} | |
+ | |
internal void WriteHeaders (bool final_flush) | |
{ | |
if (headers_sent) | |
diff --git a/System.Web/System.Web/HttpResponseWrapper.cs b/System.Web/System.Web/HttpResponseWrapper.cs | |
index 1e437e4621fc..bf603eb38125 100644 | |
--- a/System.Web/System.Web/HttpResponseWrapper.cs | |
+++ b/System.Web/System.Web/HttpResponseWrapper.cs | |
@@ -225,6 +225,9 @@ public override void AppendCookie (HttpCookie cookie) | |
w.AppendCookie (cookie); | |
} | |
+ public override ISubscriptionToken AddOnSendingHeaders(Action<HttpContextBase> callback) => | |
+ w.AddOnSendingHeaders(HttpContextWrapper.WrapCallback(callback)); | |
+ | |
public override void AppendHeader (string name, string value) | |
{ | |
w.AppendHeader (name, value); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment