Pass response body from IDispatchMessageInspector to Application_EndRequest - wcf

I want to log the request parameters and response body that traffics thru my WCF REST service . I can access full response in IDispatchMessageInspector. And I can access request headers and other items that I store in Context.Items during the operations in Application_EndRequest.
During my debugging, I see the operations goes thru IDispatchMessageInspector and then thru Application_EndRequest. My idea is to store the response somewhere in IDispatchMessageInspector and then in Application_EndRequest, I'll retrieve the response and log it together with other request parameters.
So my question is: Where should I store the response so it's accessible in Application_EndRequest?

I'm currently trying to do something similar. I am logging an incoming request, storing it in the database and would then like to pass the log ID to my endpoint for use later. In your AfterReceiveRequest call, simply add whatever you need to the IncomingMessageProperties attribute of the current operationcontext:
Edit: Fixed the code below
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);
request = buffer.CreateMessage();
int LogRequestID = Logging.LogIncomingRequest(buffer.CreateMessage());
OperationContext.Current.IncomingMessageProperties.Add("LogRequestID", LogRequestID);
return null;
}
I can then read the LogRequestID in my endpoint with the following code:
OperationContext.Current.IncomingMessageProperties["LogRequestID"]
You can also pass a more complex if you need to. Hope that helps.

Related

Post request error when sending "application/octet-stream" to an ASP.NET Core Web API service

I need to create an ASP.NET Core 3 Web API that understand this URL
http://myapp.com/MyASPNetCore3WebApi/myController/myWebMethod?user=A0001
and one zipfile which goes as a content. This is the code that calls the needed API, which I need to create:
HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(URI);
httpWebRequest.Timeout = -1;
httpWebRequest.KeepAlive = false;
httpWebRequest.Method = "POST";
httpWebRequest.ProtocolVersion = HttpVersion.Version10;
httpWebRequest.ContentType = "application/octet-stream";
httpWebRequest.Accept = "application/octet-stream";
httpWebRequest.ContentLength = data.Length;
Stream requestStream = httpWebRequest.GetRequestStream();
requestStream.Write(data, 0, data.Length);
requestStream.Close();
HttpWebResponse httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse();
The code above is working fine, it is used everyday, sending data to a java web service, now I am replacing that system for a new one in ASP.NET Core and I can't change the caller's code, that's why I need to create a Web API that understand that URL.
I have wrote this code in my Web API, but I guess I am missing something that I canĀ“t figure it out because I get an error ion the client (code above)
[HttpPost("myWebMethod")]
public FileStreamResult myWebMethod(string user, [FromBody] Stream compress)
{
byte[] zip = ((MemoryStream)compress).ToArray();
byte[] data = ZipHelper.Uncompress(zip);
.....................
}
The error I get in the client is this:-
[System.Net.WebException] {"The remote server returned an error: (415)
Unsupported Media Type."} System.Net.WebException
Thanks in advance for any help
If the goal is to read the raw request content, this can be done using HttpContext controller property. HttpContext has Request property that provides access to the actual HTTP request.
No additional model properties or controller arguments are needed to access raw request stream. It's important to note that FromBody and FromForm binding should not be used in this case.
There are couple notes regarding the code in the example from the original question.
byte[] zip = ((MemoryStream)compress).ToArray();
byte[] data = ZipHelper.Uncompress(zip);
The HttpContext.Request.Body property does not return MemoryStream, it returns its own implementation of a Stream. It means that there is no ToArray method.
When reading the entire content of a request directly into the server's memory, it is better to check the content length, otherwise the client can crash the server by sending a large enough request.
Using *Async methods when reading the content of the request will improve performance.

Read raw Request.Body in Asp.Net Core MVC Action with Route Parameters

I need to process the raw request body in and MVC core controller that has route parameters
[HttpPut]
[Route("api/foo/{fooId}")]
public async Task Put(string fooId)
{
reader.Read(Request.Body).ToList();
await _store.Add("tm", "test", data);
}
but It seems like the model binder has already consumed the request stream by the time it gets to the controller.
If I remove the route parameters, I can then access the request stream (since the framework will no longer process the stream to look for parameters).
How can I specify both route parameters and be able to access Request Body without having to manually parse request URI etc.?
I have tried decorating my parameters with [FromRoute] but it had no effect.
Please note I cannot bind the request body to an object and have framework handle the binding, as I am expecting an extremely large payload that needs to be processed in chunks in a custom manner.
There are no other controller, no custom middle-ware, filters, serialzier, etc.
I do not need to process the body several times, only once
storing the stream in a temp memory or file stream is not an options, I simply want to process the request body directly.
How can I get the framework to bind paramters from Uri, QueryString, etc. but leave the request body to me?
Define this attribute in your code:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
var factories = context.ValueProviderFactories;
factories.RemoveType<FormValueProviderFactory>();
factories.RemoveType<JQueryFormValueProviderFactory>();
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
}
If you're targeting .Net Core 3 you also need to add
factories.RemoveType<FormFileValueProviderFactory>();
Now decorate your action method with it:
[HttpPut]
[Route("api/foo/{fooId}")]
[DisableFormValueModelBinding]
public async Task Put(string fooId)
{
reader.Read(Request.Body).ToList();
await _store.Add("tm", "test", data);
}
The attribute works by removing Value Providers which will attempt to read the request body, leaving just those which supply values from the route or the query string.
HT #Tseng for the link Uploading large files with streaming which defines this attribute
As I suspected the root cause was MVC inspecting the request body in order to try to bind route parameters. This is how model binding works by default for any routes that are not parameter-less, as per documentation.
The framework however does this only when the request content type is not specified, or when it is form data (multipart or url-encoded I assume).
Changing my request content-type to any thing other than form data (e.g. application/json) I can get the framework to ignore the body unless specifically required (e.g. with a [FromBody] route parameter). This is an acceptable solution for my case since I am only interested accepting JSON payloads with content-type application/json.
Implementation of DisableFormValueModelBindingAttribute in Uploading large files with streaming pointed out by #Tseng seems to be a better approach however, so I will look into using that instead, for complete

Web API 2.0 - How to validate incoming Request in ASP.NET Web API similar in WCF AfterReceiveRequest(IDispatchMessageInspector member)

I am migrating existing WCF service in to Web API. In WCF have implemented IDispatchMessageInspector Members for validating/Logging incoming request and outgoing reply. Like below
"#region IDispatchMessageInspector Members"
public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, IClientChannel channel, InstanceContext instanceContext)
{
//Log the Request with Log4Net
Logging.InfoMessage = string.Format("{0} - {1}", "Before calling ", GetWebMethodName());
Logging.DebugMessage = request.ToString();
Logging.WriteLog();
//Logic to validate Request
}
return null;
}
public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
//Log the Response with Log4Net
Logging.DebugMessage = reply.ToString();
Logging.InfoMessage = string.Format("{0} - {1}", "After called ", GetWebMethodName());
Logging.WriteLog();
}
#endregion
I would like to achieve the same in webAPI 2.0 . Doing this all the request validation handled Globally as well Logging.
Please share your experience if you have done already..
I would suggest creating a Custom Message Handler. A custom message handler is a class that inherits from the class DelegatingHandler.
All the request coming into a WebAPI request processing cycle are first handled by Request handlers and all the responses going out are lastly processed by handler.
https://www.asp.net/media/4071077/aspnet-web-api-poster.pdf
You could use ActionFilterAttribute.
The OnActionExecuting occurs before the action method is invoked and OnActionExecuted just after. Look here to have an example on how it could be implemented.
Another way could be to create your own Owin middleware. Look here for a simple example that demonstrates how to perform some checks before invoking an action method.

Apache Http Client Put Request Error

I'm trying to upload a file using the Apache Http Client's PUT method. The code is as below;
def putFile(resource: String, file: File): (Int, String) = {
val httpClient = new DefaultHttpClient(connManager)
httpClient.getCredentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(un, pw))
val url = address + "/" + resource
val put = new HttpPut(url)
put.setEntity(new FileEntity(file, "application/xml"))
executeHttp(httpClient, put) match {
case Success(answer) => (answer.getStatusLine.getStatusCode, "Successfully uploaded file")
case Failure(e) => {
e.printStackTrace()
(-1, e.getMessage)
}
}
}
When I tried running the method, I get to see the following error:
org.apache.http.NoHttpResponseException: The target server failed to respond
at org.apache.http.impl.conn.DefaultResponseParser.parseHead(DefaultResponseParser.java:101)
at org.apache.http.impl.io.AbstractMessageParser.parse(AbstractMessageParser.java:252)
at org.apache.http.impl.AbstractHttpClientConnection.receiveResponseHeader(AbstractHttpClientConnection.java:281)
at org.apache.http.impl.conn.DefaultClientConnection.receiveResponseHeader(DefaultClientConnection.java:247)
at org.apache.http.impl.conn.AbstractClientConnAdapter.receiveResponseHeader(AbstractClientConnAdapter.java:219)
at org.apache.http.protocol.HttpRequestExecutor.doReceiveResponse(HttpRequestExecutor.java:298)
at org.apache.http.protocol.HttpRequestExecutor.execute(HttpRequestExecutor.java:125)
at org.apache.http.impl.client.DefaultRequestDirector.tryExecute(DefaultRequestDirector.java:633)
at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:454)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:820)
I do not know what has gone wrong? I'm able to do GET requests, but PUT seems not to work! Any clues as to where I should look for?
Look on the server. If GET Works, but PUT does not, then you have to figure out the receiving end.
Also, you may want to write a simple HTML File that has a form with PUT Method in it to rule out your Java Part.
As a sidenode: Its technically possible that something in between stops the request from going through or the response reaching you. Best setup a dummy HTTP Server to do the testing against.
Maybe its also a timeout issue, so the server takes to long to process your PUT.
The connection you are trying to use is a stale connection and therefore the request is failing.
But why are you only seeing an error for the PUT request and you are not seeing it for the GET request?
If you check the DefaultHttpRequestRetryHandler class you will see that by default HttpClient attempts to automatically recover from I/O exceptions. The default auto-recovery mechanism is limited to just a few exceptions that are known to be safe.
HttpClient will make no attempt to recover from any logical or HTTP protocol errors (those derived from HttpException class).
HttpClient will automatically retry those methods that are assumed to be idempotent. Your GET request, but not your PUT request!!
HttpClient will automatically retry those methods that fail with a transport exception while the HTTP request is still being transmitted to the target server (i.e. the request has not been fully transmitted to the server).
This is why you don't notice any error with your GET request, because the retry mechanism handles it.
You should define a CustomHttpRequestRetryHandler extending the DefaultHttpRequestRetryHandler. Something like this:
public class CustomHttpRequestRetryHandler extends DefaultHttpRequestRetryHandler {
#Override
public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
if(exception instanceof NoHttpResponseException) {
return true;
}
return super.retryRequest(exception, executionCount, context);
}
}
Then just assign your CustomHttpRequestRetryHandler
final HttpClientBuilder httpClientBuilder = HttpClients.custom();
httpClientBuilder.setRetryHandler(new CustomHttpRequestRetryHandler());
And that's it, now your PUT request is handled by your new RetryHandler (like the GET was by the default one)

Get stream from untyped message in streamed transfer mode

I'm trying to create streaming WCF service with contract
void Send(Message message)
I create message using
var requestMessage = Message.CreateMessage(MessageVersion.Default, "operation", new StreamableBodyWriter(inputStream));
where StreamableBodyWriter inherits from System.ServiceModel.Channels.StreamBodyWriter and overrides OnWriteBodyContents to write input stream to message.
This seems to create Message correctly on client side but on service side I'm unable to read message content. When I inspect message body looks like this on both sides:
<s:Body>... stream ...</s:Body>
But Message.IsEmpty on service side is set to true.
Is there a way to get stream from Message body on service side?