Trouble sending file through IHttpAsyncHandler - c#

I'm using a IHttpHandler to call a webservice and return the resulting byte[] to the client as a downloaded file attachment. This works fine, but when I tried changing the IHttpHandler to a IHttpAsyncHandler, the file download dialog shows, but the file does not start/finish downloading. What am I doing wrong?
<%# WebHandler Language="C#" Class="PreviewPDF" %>
using System;
using System.Web;
public class PreviewPDF : IHttpAsyncHandler
{
public void ProcessRequest(HttpContext context)
{
}
public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, Object extraData)
{
string data = "some data";
using (WebService.RequestService service = new WebService.RequestService())
{
AsyncCallback callback = new AsyncCallback(EndProcessRequest);
return service.BeginGetFile(data, callback, context);
}
}
public void EndProcessRequest(IAsyncResult result)
{
HttpContext context = result.AsyncState as HttpContext;
byte[] wsoutput;
using (WebService.RequestService service = new WebService.RequestService())
{
wsoutput = service.EndGetFile(result);
}
context.Response.ContentType = "application/octet-stream";
context.Response.ContentEncoding = System.Text.Encoding.Unicode;
context.Response.AddHeader("Content-Disposition", "attachment; filename=attachment.pdf");
using (System.IO.MemoryStream ms = new System.IO.MemoryStream(wsoutput))
{
ms.WriteTo(context.Response.OutputStream);
}
context.Response.Flush();
}
public bool IsReusable {
get {
return false;
}
}
}

Few remarks about your code:
You need to call EndGetFile on the same service instance on which you called BeginGetFile
You need to pass cb as the AsyncCallBack instead of EndProcessRequest
Here's the code with these remarks taken into account:
private class State
{
public HttpContext Context { get; set; }
public RequestService Service { get; set; }
}
public void ProcessRequest(HttpContext context)
{
throw new NotImplementedException();
}
public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, Object extraData)
{
// Don't use using block or it will dispose the service before you can call EndGetFile
var state = new State
{
Service = new RequestService(),
Context = context
};
// Pass cb here and not EndProcessRequest
return state.Service.BeginGetFile(cb, state);
}
public void EndProcessRequest(IAsyncResult result)
{
State state = result.AsyncState as State;
// Be carefull as this may throw: it is best to put it in a try/finally block
// so that you dispose properly of the service
byte[] buffer = state.Service.EndGetFile(result);
state.Service.Dispose();
state.Context.Response.ContentType = "application/octet-stream";
state.Context.Response.AddHeader("Content-Disposition", "attachment; filename=attachment.pdf");
// Write directly into the output stream, and don't call Flush
state.Context.Response.OutputStream.Write(buffer, 0, buffer.Length);
}
public bool IsReusable
{
get { return false; }
}

Related

How can I listen in real-time for webhooks in asp.net webforms?

I'm trying to capture the webhooks from the site https://www.unbounce.com in my asp.net web forms application.
I've created HttpAsyncHandler in WebHookHandler.cs
public class WebHookHandler:IHttpAsyncHandler
{
public WebHookHandler()
{
//
// TODO: Add constructor logic here
//
}
public bool IsReusable { get { return false; } }
public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
{
context.Response.Write("<p>Begin IsThreadPoolThread is " + Thread.CurrentThread.IsThreadPoolThread + "</p>\r\n");
AsynchOperation asynch = new AsynchOperation(cb, context, extraData);
asynch.StartAsyncWork();
return asynch;
}
public void EndProcessRequest(IAsyncResult result)
{
}
public void ProcessRequest(HttpContext context)
{
throw new InvalidOperationException();
}
}
class AsynchOperation : IAsyncResult
{
private bool _completed;
private object _state;
private AsyncCallback _callback;
private HttpContext _context;
bool IAsyncResult.IsCompleted { get { return _completed; } }
WaitHandle IAsyncResult.AsyncWaitHandle { get { return null; } }
object IAsyncResult.AsyncState { get { return _state; } }
bool IAsyncResult.CompletedSynchronously { get { return false; } }
public AsynchOperation(AsyncCallback callback, HttpContext context, object state)
{
_callback = callback;
_context = context;
_state = state;
_completed = false;
}
public void StartAsyncWork()
{
ThreadPool.QueueUserWorkItem(new WaitCallback(StartAsyncTask), null);
}
private void StartAsyncTask(object workItemState)
{
_context.Response.Write("<p>Completion IsThreadPoolThread is " + Thread.CurrentThread.IsThreadPoolThread + "</p>\r\n");
_context.Response.Write("Hello World from Async Handler!\r\n");
using (var reader = new StreamReader(_context.Request.InputStream))
{
string postData = reader.ReadToEnd();
_context.Response.Write(postData);
}
_completed = true;
_callback(this);
}
}
and register my handler and add the map in web.config
<add verb="*" path="webhook.handler" name="WebHookAsyncHandler" type="WebHookHandler"/>
This all is actually taken from msdn (no reputation, sorry)
Next, the other site (unbounce.com) POST something like this:
data.json: {"time_submitted":["04:59 PM UTC"],"page_uuid":["3282389-f13a-44b0-9a49-6321b515d43"],"email":["test#test.com"],"page_name":["Test name"],"date_submitted":["2017-07-17"],"name":["tester"],"ip_address":["80.80.80.80"],"page_url":["http://somepage.url"],"variant":["a"]}
everytime the user presses the button. The POST url is: example.com/webhook.handler
But I don't get the posted data. The output is:
Begin IsThreadPoolThread is True
Completion IsThreadPoolThread is True
Hello World from Async Handler!
I tried also use _context.Request and _context.Request.Form before StreamReader, but they was NULL everytime.
I think, I have some global misunderstading of how these things work. Can you please help me to display the data from the POST request to my site on the page?
Well, it turned out, that everything was ok in my listing.
You just need to set up a good testing enviroment to catch these POSTs, or better, to do them yourself.

Mono: IHttpAsyncHandler doesn't call EndProcessRequest

I use ASP.NET IHttpAsyncHandler for async redirect Long Polling HTTP Requsets to other URL. It works perfectly with .NET 4.5 (Windows 7,8). But doesn't work with Mono (Mono JIT compiler version 3.2.8 (Debian 3.2.8+dfsg-4ubuntu1), Ubuntu 14.04). After completing request.BeginGetResponse AsyncCallback doesn't call EndProcessRequest.
public bool IsReusable { get { return true; } }
public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
{
var request = (HttpWebRequest)HttpWebRequest.Create("http://www.google.com/");
request.Method = context.Request.HttpMethod;
request.UserAgent = context.Request.UserAgent;
request.Accept = string.Join(",", context.Request.AcceptTypes);
if (!string.IsNullOrEmpty(context.Request.Headers["Accept-Encoding"]))
{
request.Headers["Accept-Encoding"] = context.Request.Headers["Accept-Encoding"];
}
request.ContentType = context.Request.ContentType;
request.ContentLength = context.Request.ContentLength;
using (var stream = request.GetRequestStream())
{
CopyStream(context.Request.InputStream, stream);
}
return request.BeginGetResponse(cb, new object[] { context, request });
}
public void EndProcessRequest(IAsyncResult result)
{
// EndProcessRequest never called
var context = (HttpContext)((object[])result.AsyncState)[0];
var request = (HttpWebRequest)((object[])result.AsyncState)[1];
using (var response = request.EndGetResponse(result))
{
context.Response.ContentType = response.ContentType;
foreach (string h in response.Headers)
{
context.Response.AppendHeader(h, response.Headers[h]);
}
using (var stream = response.GetResponseStream())
{
CopyStream(stream, context.Response.OutputStream);
}
response.Close();
context.Response.Flush();
}
}
private void CopyStream(Stream from, Stream to)
{
var buffer = new byte[1024];
while (true)
{
var read = from.Read(buffer, 0, buffer.Length);
if (read == 0) break;
to.Write(buffer, 0, read);
}
}
I don't know reason of this strange beahaviour. I suppose this behavior is bug of HttpWebRequest class in Mono framework but I am not sure. May be are there any workarounds of this problem?
We found some workaround of the problem by using ThreadPool.QueueUserWorkItem:
public bool IsReusable { get { return true; }}
public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
{
return new AsynchOperation(cb, context, extraData).Start();
}
public void EndProcessRequest(IAsyncResult result) { }
public void ProcessRequest(HttpContext context) { }
private class AsynchOperation : IAsyncResult
{
private AsyncCallback cb;
private HttpContext context;
public WaitHandle AsyncWaitHandle { get { return null; } }
public object AsyncState { get; private set; }
public bool IsCompleted { get; private set; }
public bool CompletedSynchronously { get { return false; } }
public AsynchOperation(AsyncCallback callback, HttpContext context, object state)
{
cb = callback;
this.context = context;
AsyncState = state;
IsCompleted = false;
}
public IAsyncResult Start()
{
ThreadPool.QueueUserWorkItem(AsyncWork, null);
return this;
}
private void AsyncWork(object _)
{
var request = (HttpWebRequest)WebRequest.Create(boshUri);
request.Method = context.Request.HttpMethod;
// copy headers & body
request.UserAgent = context.Request.UserAgent;
request.Accept = string.Join(",", context.Request.AcceptTypes);
if (!string.IsNullOrEmpty(context.Request.Headers["Accept-Encoding"]))
{
request.Headers["Accept-Encoding"] = context.Request.Headers["Accept-Encoding"];
}
request.ContentType = context.Request.ContentType;
request.ContentLength = context.Request.ContentLength;
using (var stream = request.GetRequestStream())
{
CopyStream(context.Request.InputStream, stream);
}
request.BeginGetResponse(EndGetResponse, Tuple.Create(context, request));
}
private void EndGetResponse(IAsyncResult ar)
{
var data = (Tuple<HttpContext, HttpWebRequest>)ar.AsyncState;
var context = data.Item1;
var request = data.Item2;
try
{
using (var response = request.EndGetResponse(ar))
{
context.Response.ContentType = response.ContentType;
// copy headers & body
foreach (string h in response.Headers)
{
context.Response.AppendHeader(h, response.Headers[h]);
}
using (var stream = response.GetResponseStream())
{
CopyStream(stream, context.Response.OutputStream);
}
context.Response.Flush();
}
}
catch (Exception err)
{
if (err is IOException || err.InnerException is IOException)
{
// ignore
}
else
{
LogManager.GetLogger("ASC.Web.BOSH").Error(err);
}
}
finally
{
IsCompleted = true;
cb(this);
}
}

How to send multiple acknowledgement to client using IHttpAsyncHandler in asp.net

I have implemented IHttpAsyncHandler in my class to perform 5-6 long running process in background and acknowledge to client on start of each task.
Earlier I was using one session variable and updating it with current status of task, and giving async call request to server from jquery in interval of 5 seconds to get current status, but this implementation is not good because its continually hitting request to server for status.
Then I implemented IHttpAsyncHandler in my application, now server itself send acknowledgement to client, but as per my implementation I am able to send only one acknowledgement! if I try to send more than one then its giving error as "object reference not set to an instance of an object"
please check my sample code.
in my code
ExecuteFirst() method works fine sending acknowledgement to client but ExecuteSecond() does not send acknowledgement its giving error.
I goggled a lot but not getting proper way to send multiple acknowledgement to client.
this is my sample code, please help me if any one have any idea.
Javascript
function postRequest(url) {
var url = 'StartLongRunningProcess.ashx?JOBCODE=18'
var xmlhttp = getRequestObject();
xmlhttp.open("POST", url, true);
xmlhttp.onreadystatechange =
function () {
if (xmlhttp.readyState == 4) {
var response = xmlhttp.responseText;
divResponse.innerHTML += "<p>" + response + "</p>";
}
}
xmlhttp.send();
}
function getRequestObject() {
var req;
if (window.XMLHttpRequest && !(window.ActiveXObject)) {
try {
req = new XMLHttpRequest();
}
catch (e) {
req = false;
}
}
else if (window.ActiveXObject) {
try {
req = new ActiveXObject('Msxml2.XMLHTTP');
}
catch (e) {
try {
req = new ActiveXObject('Microsoft.XMLHTTP');
}
catch (e) {
req = false;
}
}
}
return req;
}
StartLongRunningProcess.ashx.cs
public class StartLongRunningProcess: IHttpAsyncHandler, IRequiresSessionState
{
private AsyncRequestResult _asyncResult;
public void ProcessRequest(HttpContext context) {}
public bool IsReusable
{
get { return true;}
}
public IAsyncResult BeginProcessRequest(HttpContext context, System.AsyncCallback cb, object extraData)
{
int32 jobCode= convert.ToInt32(context.Request["JOBCODE"]);
_asyncResult = new AsyncRequestResult(context, cb, jobCode);
if(jobCode==18)
{
StartProcess18()
}
else
{
//StartProcessOther()
}
}
private StartProcess18()
{
var task1= new Task(() =>
{
ExecuteFirst();
});
var task2 = task1.ContinueWith((t1) =>
{
ExecuteSecond();
}, TaskContinuationOptions.OnlyOnRanToCompletion);
task1.Start();
}
private ExecuteFirst()
{
//notify to client that this job has been started
_asyncResult.CurrentContext.Response.Write("First task has been started");
_asyncResult.Notify();
// Above line of code successfully send a acknowledgement to client
//Doing some long running process
}
private ExecuteSecond()
{
//notify to client that this job has been started
_asyncResult.CurrentContext.Response.Write("Second task has been started");
// Above line of code giving error and if I skip it and call Notify() this also does not work.
_asyncResult.Notify();
//Doing some long running process
}
public void EndProcessRequest(IAsyncResult result)
{
}
}
AsyncRequestResult.cs
public class AsyncRequestResult : IAsyncResult
{
private HttpContext context;
private AsyncCallback callback;
private ManualResetEvent completeEvent = null;
private object data;
private object objLock = new object();
private bool isComplete = false;
public AsyncRequestResult(HttpContext ctx, AsyncCallback cb, object d)
{
this.context = ctx;
this.callback = cb;
this.data = d;
}
public HttpContext Context
{
get { return this.context; }
}
public void Notify()
{
//isComplete = true;
lock(objLock)
{
if(completeEvent != null)
{
completeEvent.Set();
}
}
if (callback != null)
{
callback(this);
}
}
public object AsyncState
{
get { return this.data; }
}
public bool CompletedSynchronously
{
get { return false; }
}
public WaitHandle AsyncWaitHandle
{
get
{
lock(objLock)
{
if (completeEvent == null)
completeEvent = new ManualResetEvent(false);
return completeEvent;
}
}
}
public bool IsCompleted
{
get { return this.isComplete; }
}
}
HttpContext.Current is not null only if you access it in a thread that handles incoming requests.
Your continuation task that is running is most likely running on a ThreadPool thread without the HttpContext.Current flowing to the continuation, hence it being null.
Try setting your TaskScheduler to TaskScheduler.FromCurrentSynchronizationContext() in order to execute it in the same context.
private StartProcess18()
{
var task1= new Task(() =>
{
ExecuteFirst();
});
var task2 = task1.ContinueWith((t1) =>
{
ExecuteSecond();
}, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext());
task1.Start();
}

How to make a WCF REST method entirely asynchronous with the Task Parallel Library?

I am trying to make a WCF REST method entirely asynchronous (I don't want to block anywhere). Essentially I have a simple service with 3 layers: Service, Business Logic and Data Access Layer. The Data Access Layer is accessing a database and it can take several second to get a response back from that method.
I don't understand very well how to chaining of all those method work. Can someone please help me to complete the sample I am trying to write below? I don't understand well the pattern used by WCF and I didn't find much documentation on the subject.
Can someone help me to complete the following example? In addition, how can I measure that the service will be able to handle more load than a typical synchronous implementation?
using System;
using System.Collections.Generic;
using System.Runtime.Remoting.Messaging;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using System.Threading.Tasks;
namespace WcfRestService1
{
[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode =
AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class Service1
{
private BusinessLogic bll = new BusinessLogic();
// Synchronous version
[WebGet(UriTemplate = "/sync")]
public string GetSamples()
{
return bll.ComputeData();
}
// Asynchronous version - Begin
[WebGet(UriTemplate = "/async")]
[OperationContract(AsyncPattern = true)]
public IAsyncResult BeginGetSampleAsync(AsyncCallback callback,
object state)
{
Task<string> t = bll.ComputeDataAsync();
// What am I suppose to return here
// return t.AsyncState; ???
}
// Asynchronous version - End
public List<SampleItem> EndGetSampleAsync(IAsyncResult result)
{
// How do I handle the callback here?
}
}
public class BusinessLogic
{
public Task<string> ComputeDataAsync()
{
DataAccessLayer dal = new DataAccessLayer();
return dal.GetData();
}
public string ComputeData()
{
Task<string> t = this.ComputeDataAsync();
// I am blocking... Waiting for the data
t.Wait();
return t.Result;
}
}
public class DataAccessLayer
{
public Task<string> GetData()
{
// Read data from disk or network or db
}
}
}
Here's an example. I got it working with help from the following posts:
Edit: Added an example of an async client
Implement Classic Async Pattern using TPL
http://pfelix.wordpress.com/2008/06/27/wcf-and-the-asyncpattern-property-part-1/
http://pfelix.wordpress.com/2008/06/28/wcf-and-the-asyncpattern-part-2/
Here's a little do-nothing service:
namespace WcfAsyncTest
{
[ServiceContract]
public interface IAsyncTest
{
[OperationContract(AsyncPattern=true)]
IAsyncResult BeginOperation(AsyncCallback callback, object state);
string EndOperation(IAsyncResult ar);
}
// NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Service1" in code, svc and config file together.
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class Service1 : IAsyncTest
{
public IAsyncResult BeginOperation(AsyncCallback callback, object state)
{
Task result = Task.Factory.StartNew((x) =>
{
// spin to simulate some work
var stop = DateTime.Now.AddSeconds(10);
while (DateTime.Now < stop)
Thread.Sleep(100);
}, state);
if (callback != null)
result.ContinueWith(t => callback(t));
return result;
}
public string EndOperation(IAsyncResult ar)
{
ar.AsyncWaitHandle.WaitOne();
return "Hello!!";
}
}
}
And here's the client (command line):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace TestClient
{
class Program
{
static void Main(string[] args)
{
var client = new ServiceReference1.AsyncTestClient();
var result = client.Operation();
Console.WriteLine(result);
Console.ReadLine();
}
}
}
if you put trace points on the service, you can see that WCF really is calling EndOperation for you.
Async Client Example
First, you will need to generate an async proxy. You can do that by right-clicking on the Service Reference (in the References Folder of your project), and choosing "Configure Service Reference". Check the "Generate Asynchronous Operations" checkbox.
Now your client proxy will have some new members that weren't there before. Here's how to use them:
// this is in the command-line test client
// no changes to your service required.
static void AsyncTest()
{
var client = new ServiceReference1.AsyncTestClient();
client.OperationCompleted += new EventHandler(client_OperationCompleted);
client.OperationAsync();
Console.WriteLine("Operation Running");
}
static void client_OperationCompleted(object sender, ServiceReference1.OperationCompletedEventArgs e)
{
if (e.Error == null)
Console.WriteLine("Operation Complete. Result: " + e.Result);
else
Console.WriteLine(e.Error.ToString());
}
here is an implementation of a service that implements Async. In this the callback of wcf is passed all the way to the ado.net's sql command. When the command returns, it would invoke the service's EndXXX method, which would invoke Business layer, which would finally invoke EndXXX of SqlCommand. Let me know if you face any issues
public class Service
{
private BusinessLogic businessLayer = new BusinessLogic();
public IAsyncResult BeginAnyOperation(AsyncCallback callback, object userState)
{
return businessLayer.BeginComputeData(callback, userState);
}
public string EndAnyOperation(IAsyncResult result)
{
return businessLayer.EndComputeDate(result);
}
}
public class MyState<T> : IAsyncResult
{
public MyState() { }
public object AsyncState { get; set; }
public WaitHandle AsyncWaitHandle { get; set; }
public bool CompletedSynchronously
{
get { return true; }
}
public bool IsCompleted { get; set; }
public AsyncCallback AsyncCallback { get; set; }
public T Result { get; set; }
public IAsyncResult InnerResult { get; set; }
}
public class BusinessLogic
{
private DataAccessLayer dal = new DataAccessLayer();
public IAsyncResult BeginComputeData(AsyncCallback callback, object state)
{
return dal.BeginGetData(callback, state);
}
public string EndComputeDate(IAsyncResult asyncResult)
{
return dal.EndGetData(asyncResult);
}
}
public class DataAccessLayer
{
public IAsyncResult BeginGetData(AsyncCallback callback, object state)
{
var conn = new SqlConnection("");
conn.Open();
SqlCommand cmd = new SqlCommand("myProc", conn);
var commandResult = cmd.BeginExecuteReader(callback, state, System.Data.CommandBehavior.CloseConnection);
return new MyState<string> { AsyncState = cmd, InnerResult = commandResult };
}
public string EndGetData(IAsyncResult result)
{
var state = (MyState<string>)result;
var command = (SqlCommand)state.AsyncState;
var reader = command.EndExecuteReader(state.InnerResult);
if (reader.Read())
return reader.GetString(0);
return string.Empty;
}
}

How do I get the XML SOAP request of an WCF Web service request?

I'm calling this web service within code and I would like to see the XML, but I can't find a property that exposes it.
I think you meant that you want to see the XML at the client, not trace it at the server. In that case, your answer is in the question I linked above, and also at How to Inspect or Modify Messages on the Client. But, since the .NET 4 version of that article is missing its C#, and the .NET 3.5 example has some confusion (if not a bug) in it, here it is expanded for your purpose.
You can intercept the message before it goes out using an IClientMessageInspector:
using System.ServiceModel.Dispatcher;
public class MyMessageInspector : IClientMessageInspector
{ }
The methods in that interface, BeforeSendRequest and AfterReceiveReply, give you access to the request and reply. To use the inspector, you need to add it to an IEndpointBehavior:
using System.ServiceModel.Description;
public class InspectorBehavior : IEndpointBehavior
{
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.MessageInspectors.Add(new MyMessageInspector());
}
}
You can leave the other methods of that interface as empty implementations, unless you want to use their functionality, too. Read the how-to for more details.
After you instantiate the client, add the behavior to the endpoint. Using default names from the sample WCF project:
ServiceReference1.Service1Client client = new ServiceReference1.Service1Client();
client.Endpoint.Behaviors.Add(new InspectorBehavior());
client.GetData(123);
Set a breakpoint in MyMessageInspector.BeforeSendRequest(); request.ToString() is overloaded to show the XML.
If you are going to manipulate the messages at all, you have to work on a copy of the message. See Using the Message Class for details.
Thanks to Zach Bonham's answer at another question for finding these links.
Option 1
Use message tracing/logging.
Have a look here and here.
Option 2
You can always use Fiddler to see the HTTP requests and response.
Option 3
Use System.Net tracing.
I just wanted to add this to the answer from Kimberly. Maybe it can save some time and avoid compilation errors for not implementing all methods that the IEndpointBehaviour interface requires.
Best regards
Nicki
/*
// This is just to illustrate how it can be implemented on an imperative declarared binding, channel and client.
string url = "SOME WCF URL";
BasicHttpBinding wsBinding = new BasicHttpBinding();
EndpointAddress endpointAddress = new EndpointAddress(url);
ChannelFactory<ISomeService> channelFactory = new ChannelFactory<ISomeService>(wsBinding, endpointAddress);
channelFactory.Endpoint.Behaviors.Add(new InspectorBehavior());
ISomeService client = channelFactory.CreateChannel();
*/
public class InspectorBehavior : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
// No implementation necessary
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.MessageInspectors.Add(new MyMessageInspector());
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
// No implementation necessary
}
public void Validate(ServiceEndpoint endpoint)
{
// No implementation necessary
}
}
public class MyMessageInspector : IClientMessageInspector
{
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
// Do something with the SOAP request
string request = request.ToString();
return null;
}
public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
// Do something with the SOAP reply
string replySoap = reply.ToString();
}
}
OperationContext.Current.RequestContext.RequestMessage
this context is accesible server side during processing of request.
This doesn`t works for one-way operations
Simply we can trace the request message as.
OperationContext context = OperationContext.Current;
if (context != null && context.RequestContext != null)
{
Message msg = context.RequestContext.RequestMessage;
string reqXML = msg.ToString();
}
I am using below solution for IIS hosting in ASP.NET compatibility mode. Credits to Rodney Viana's MSDN blog.
Add following to your web.config under appSettings:
<add key="LogPath" value="C:\\logpath" />
<add key="LogRequestResponse" value="true" />
Replace your global.asax.cs with below (also fix namespace name):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.SessionState;
using System.Text;
using System.IO;
using System.Configuration;
namespace Yournamespace
{
public class Global : System.Web.HttpApplication
{
protected static bool LogFlag;
protected static string fileNameBase;
protected static string ext = "log";
// One file name per day
protected string FileName
{
get
{
return String.Format("{0}{1}.{2}", fileNameBase, DateTime.Now.ToString("yyyy-MM-dd"), ext);
}
}
protected void Application_Start(object sender, EventArgs e)
{
LogFlag = bool.Parse(ConfigurationManager.AppSettings["LogRequestResponse"].ToString());
fileNameBase = ConfigurationManager.AppSettings["LogPath"].ToString() + #"\C5API-";
}
protected void Session_Start(object sender, EventArgs e)
{
}
protected void Application_BeginRequest(object sender, EventArgs e)
{
if (LogFlag)
{
// Creates a unique id to match Rquests with Responses
string id = String.Format("Id: {0} Uri: {1}", Guid.NewGuid(), Request.Url);
FilterSaveLog input = new FilterSaveLog(HttpContext.Current, Request.Filter, FileName, id);
Request.Filter = input;
input.SetFilter(false);
FilterSaveLog output = new FilterSaveLog(HttpContext.Current, Response.Filter, FileName, id);
output.SetFilter(true);
Response.Filter = output;
}
}
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
}
protected void Application_Error(object sender, EventArgs e)
{
}
protected void Session_End(object sender, EventArgs e)
{
}
protected void Application_End(object sender, EventArgs e)
{
}
}
class FilterSaveLog : Stream
{
protected static string fileNameGlobal = null;
protected string fileName = null;
protected static object writeLock = null;
protected Stream sinkStream;
protected bool inDisk;
protected bool isClosed;
protected string id;
protected bool isResponse;
protected HttpContext context;
public FilterSaveLog(HttpContext Context, Stream Sink, string FileName, string Id)
{
// One lock per file name
if (String.IsNullOrWhiteSpace(fileNameGlobal) || fileNameGlobal.ToUpper() != fileNameGlobal.ToUpper())
{
fileNameGlobal = FileName;
writeLock = new object();
}
context = Context;
fileName = FileName;
id = Id;
sinkStream = Sink;
inDisk = false;
isClosed = false;
}
public void SetFilter(bool IsResponse)
{
isResponse = IsResponse;
id = (isResponse ? "Reponse " : "Request ") + id;
//
// For Request only read the incoming stream and log it as it will not be "filtered" for a WCF request
//
if (!IsResponse)
{
AppendToFile(String.Format("at {0} --------------------------------------------", DateTime.Now));
AppendToFile(id);
if (context.Request.InputStream.Length > 0)
{
context.Request.InputStream.Position = 0;
byte[] rawBytes = new byte[context.Request.InputStream.Length];
context.Request.InputStream.Read(rawBytes, 0, rawBytes.Length);
context.Request.InputStream.Position = 0;
AppendToFile(rawBytes);
}
else
{
AppendToFile("(no body)");
}
}
}
public void AppendToFile(string Text)
{
byte[] strArray = Encoding.UTF8.GetBytes(Text);
AppendToFile(strArray);
}
public void AppendToFile(byte[] RawBytes)
{
bool myLock = System.Threading.Monitor.TryEnter(writeLock, 100);
if (myLock)
{
try
{
using (FileStream stream = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
stream.Position = stream.Length;
stream.Write(RawBytes, 0, RawBytes.Length);
stream.WriteByte(13);
stream.WriteByte(10);
}
}
catch (Exception ex)
{
string str = string.Format("Unable to create log. Type: {0} Message: {1}\nStack:{2}", ex, ex.Message, ex.StackTrace);
System.Diagnostics.Debug.WriteLine(str);
System.Diagnostics.Debug.Flush();
}
finally
{
System.Threading.Monitor.Exit(writeLock);
}
}
}
public override bool CanRead
{
get { return sinkStream.CanRead; }
}
public override bool CanSeek
{
get { return sinkStream.CanSeek; }
}
public override bool CanWrite
{
get { return sinkStream.CanWrite; }
}
public override long Length
{
get
{
return sinkStream.Length;
}
}
public override long Position
{
get { return sinkStream.Position; }
set { sinkStream.Position = value; }
}
//
// For WCF this code will never be reached
//
public override int Read(byte[] buffer, int offset, int count)
{
int c = sinkStream.Read(buffer, offset, count);
return c;
}
public override long Seek(long offset, System.IO.SeekOrigin direction)
{
return sinkStream.Seek(offset, direction);
}
public override void SetLength(long length)
{
sinkStream.SetLength(length);
}
public override void Close()
{
sinkStream.Close();
isClosed = true;
}
public override void Flush()
{
sinkStream.Flush();
}
// For streamed responses (i.e. not buffered) there will be more than one Response (but the id will match the Request)
public override void Write(byte[] buffer, int offset, int count)
{
sinkStream.Write(buffer, offset, count);
AppendToFile(String.Format("at {0} --------------------------------------------", DateTime.Now));
AppendToFile(id);
AppendToFile(buffer);
}
}
}
It should create log file in the folder LogPath with request and response XML.
There is an another way to see XML SOAP - custom MessageEncoder. The main difference from IClientMessageInspector is that it works on lower level, so it captures original byte content including any malformed xml.
In order to implement tracing using this approach you need to wrap a standard textMessageEncoding with custom message encoder as new binding element and apply that custom binding to endpoint in your config.
Also you can see as example how I did it in my project -
wrapping textMessageEncoding, logging encoder, custom binding element and config.

Categories