WebAPI - Log binding information at MessageHandler? - c#

I need to log (not Trace) all requests and responses from webAPI (v1) and store the information in DB.
I thought that the most appropriate place to do it is via a MessageHandler.
So :
public class LogRequestAndResponseHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
return base.SendAsync(request, cancellationToken)
.ContinueWith(task =>
{
var MyRequest = request;
var MyResponse = task.Result;
var responseBody = task.Result.Content;
//log db....
return task.Result;
}, cancellationToken);
}
}
Please notice that I'm reading the request and response after the SendAsync ( in a continuation) because I want a single write to DB.
But:
I thought to myself : "it would be nice if I could extract the method binding information at the same place (in the MessageHandler)"
For example , consider this code :
[HttpGet]
[ActionName("GetGraph")]
public HttpResponseMessage BlaBla(string Member_ID, int DateDiff)
{
//...
}
And this request :
http://es.com/api/claims/GetGraph?Member_ID=123&DateDiff=5&NotExists=2
Question:
Is it possible(and how) to extract something like this :
User sent match-able Member_ID with value 123
User sent match-able DateDiff with value 5
User sent non-match-able NotExists with value 2
At this stage :

You are correct about using a message handler for logging the raw request and response but then were you able to log the request body using the code above? If you actually bind the body to an action method parameter, parameter binding would actually read and empty the request body stream. So, I don't think your idea of logging the request body in task continuation will work.
Logging data about binding in a message handler is complicated and depends a lot on your action methods. Message handler request processing runs before model binding and when the response processing part runs, the notion of models do not exist, since serialization is already done. BTW, take a look at my blog post for detecting extra fields in the request, when you are using JSON payload.
http://lbadri.wordpress.com/2014/01/28/detecting-extra-fields-in-asp-net-web-api-request/
You can possibly write an action filter to look at the extra fields from the model, as mentioned in that post and store that info in request dictionary (Request.Properties) and pick that up later in the message handler and write to database. It is not straight forward though.
Alternatively, look at the built-in tracer to see if it is helpful.

Related

How to get request body data from System.Mvc.Web.ActionExecutingContext in c#

Here is my method It will take only one parameter . But I want user whaterver add in request body I need to save in logging.
[HttpPost]
[LogAPIUser]
public async Task<JsonResult> GameDetail(long game)
Here is my Postman request
In ActionExecutingContext I have got only one action parameter
How can I get all body request data?
If anyone have idea please let me know
Thanks in advance.
https://learn.microsoft.com/en-us/aspnet/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api states that:
At most one parameter is allowed to read from the message body.
The reason for this rule is that the request body might be stored in a non-buffered stream that can only be read once.
You can try to look at this question:
WebAPI Multiple Put/Post parameters
basically, you read the whole body, and get your elements out of it.
something in the effect of
[HttpPost]
public string MyMethod([FromBody]JObject data)
{
long game = data["game"].ToObject<Long>();
long rer = data["rer"].ToObject<Long>();
}
(did not try code, might be buggy)

Process incoming FileStream asynchronously

I'm reading a file from user upload and it was working synchronously. I needed to change it in order to immediately send a "received" alert to the user, then read the file asynchronously while the user would periodically poll back to see if the read was finished.
Here is what my code looks like right now:
public FileUpload SaveFile(Stream stream)
{
FileUpload uploadObj = //instantiate the return obj
var task = Task.Run(async () => await ProcessFileAsync(stream));
return upload;
}
public async Task ProcessFileAsync(Stream stream)
{
StreamReader file = new StreamReader(stream);
CsvReader csv = new CsvReader(file, CultureInfo.InvariantCulture);
while (await csv.ReadAsync())
{
//read the file
}
}
the issue I'm having is that by the time I call the csv.ReadAsync() method, the Stream object has been disposed. How do I access the Stream when I want the SaveFile() method to return a value to the user, but the act of returning disposes the Stream object?
The point here is that you're working within the constraints of ASP.NET, which abstracts away a lot of the underlying HTTP stuff.
When you say you want to process a user-uploaded file asynchronously, you want to step out of the normal order of doing things with HTTP and ASP.NET. You see, when a client sends a request with a body (the file), the server receives the request headers and kicks off ASP.NET to tell your application code that there's a new request incoming.
It hasn't even (fully) read the request body at this point. This is why you get a Stream to deal with the request, and not a string or a filename - the data doesn't have to be arrived at the server yet! Just the request headers, informing the web server about the request.
If you return a response at that point, for all HTTP and ASP.NET care, you're done with the request, and you cannot continue reading its body.
Now what you want to do, is to read the request body (the file), and process that after sending a response to the client. You can do that, but then you'll still have to read the request body - because if you return something from your action method before reading the request, the framework will think you're done with it and dispose the request stream. That's what's causing your exception.
If you'd use a string, or model binding, or anything that involves the framework reading the request body, then yes, your code will only execute once the body has been read.
The short-term solution that would appear to get you going, is to read the request stream into a stream that you own, not the framework:
var myStream = new MemoryStream();
await stream.CopyTo(myStream);
Task.Run(async () => await ProcessFileAsync(myStream));
Now you'll have read the entire request body and saved it in memory, so ASP.NET can safely dispose the request stream and send a response to the client.
But don't do this. Starting fire-and-forget tasks from a controller is a bad idea. Keeping uploaded files in memory is a bad idea.
What you actually should do, if you still want to do this out-of-band:
Save the incoming file as an actual, temporary file on your server
Send a response to the client with an identifier (the temporarily generated filename, for example a GUID)
Expose an endpoint that clients can use to request the status using said GUID
Have a background process continuously scan the directory for newly uploaded files and process them
For the latter you could hosted services or third-party tools like Hangfire.
You'll need to either do this if the environment warrants:
var result = task.Result;
//do stuff
...or
public Task<FileUpload> SaveFile(Stream stream)
{
var uploadObj = //instantiate the return obj
await ProcessFileAsync(stream);
return uploadObj;
}
See here for a thorough discussion on fire-and-forget if you go that route:
Web Api - Fire and Forget

How to handle PostJsonAsync() call

Currently I have 3rd party WebApi which has the following external call using Flurl builder.
await _client.Url.ToString().PostJsonAsync(data);
And I'm trying to handle the response with such endpoint:
[HttpPost]
public void HandleResponse(HttpResponseMessage response)
{
}
The response message is with status OK but has Content and Headers = null
How can I handle this properly?
This API endpoint doesn't make any sense to me:
[HttpPost]
public void HandleResponse(HttpResponseMessage response)
{
//...
}
The endpoint would be handling a request and returning a response, not the other way around. Something more like this:
[HttpPost]
public HttpResponseMessage HandleResponse(HttpRequestMessage request)
{
//...
}
When something contacts an API (or server of any kind, really), it's sending a request to that API. That API receives the request and returns a response. The semantics of these two words pretty much describe the process, it's very important to keep them straight.
Consider it like this... If someone asks you a question, what you receive is a question. Not an answer. What you send back to that person is the answer.

C# Web API Request/Response Logging with OAuth

I am having a tough time figuring this out, but when I use a custom message handler in my Web API (created in VS2013) none of the /token request get processed through my handler.
What I'm trying to do is assist our support crew by implementing some logging to save request / response values for a few days. This will allow them to see the request and responses as raw as possible.
It's working, for everything except "/token" requests. We need to process the requests and responses for "/token" and "/authenticate" as a large percentage of our support calls end up being username and password issues.
I also need to do this in a message handler so I can isolate the code to message handlers.
Here is a sample handler I'm testing with in an isolated project. It's only in place ATM to debug/test this issue. I've also implemented a DelegatingHandler as well with the same results.
public class MyMessageProcessingHandler : MessageProcessingHandler {
protected override HttpRequestMessage ProcessRequest(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) {
Trace.WriteLine(string.Format("{0} {1}", request.Method, request.RequestUri));
return request;
}
protected override HttpResponseMessage ProcessResponse(HttpResponseMessage response, System.Threading.CancellationToken cancellationToken) {
Trace.WriteLine("response!");
return response;
}
}
in WebApiConfig.Register method I add the message handler to the config's message handler collection.
(I also tried Global.asax.cs is the Application_Start method)
GlobalConfiguration.Configuration.MessageHandlers.Add(new MyMessageProcessingHandler());
The order doesn't seem to matter - I've tried it as the first line of code, or the last.I've tried to Insert after the passive message handler is added by
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
GlobalConfiguration.Configuration.MessageHandlers.Insert(0, new MyMessageProcessingHandler());
Whenever I make an api/Values request or any api/Controller request the custom message handler handles the request just fine. However, when I post (or get) to /token (yes-properly) the custom message handler doesn't process the request.
I would like to know how to use a Message Handler to process the /token & /authenticate requests. I appreciate all your help!
Thanks,
-Rick
I went with a custom IHttpModule. It ended doing what I wanted in the way I wanted it by giving me direct access to the requests and allowing me to inspect them; even the authentication requests.
Thanks to all that looked at my issue.

Web API response time

I'm building a server monitoring system and want to send a request to a Web API and get the server's health in a JSON object if it's ok, if its database connections are working, its response time, etc.
How can I implement the response time, saying how long the Web API takes to answer the request?
If you want to implement Web Api Monitoring, you can create a custom DelegatingHandler to track action duration and status.
Here is a very basic example to measure operation duration. The duration is added to response (quite useless) ; it's better to store this kind of data to a dedicated repository.
public class MonitoringDelegate : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
var watcher = Stopwatch.StartNew();
var response = await base.SendAsync(request, cancellationToken);
watcher.Stop();
//store duration somewheren here in the response header
response.Headers.Add("X-Duration", watcher.ElapsedMilliseconds.ToString());
return response;
}
}
You could start a Stopwatch on your client and stop it when you recive your awnser

Categories