Why is the body of a Web API request read once? - asp.net

My goal is to authenticate Web API requests using a AuthorizationFilter or DelegatingHandler. I want to look for the client id and authentication token in a few places, including the request body. At first it seemed like this would be easy, I could do something like this
var task = _message.Content.ReadAsAsync<Credentials>();
task.Wait();
if (task.Result != null)
{
// check if credentials are valid
}
The problem is that the HttpContent can only be read once. If I do this in a Handler or a Filter then the content isn't available for me in my action method. I found a few answers here on StackOverflow, like this one: Read HttpContent in WebApi controller that explain that it is intentionally this way, but they don't say WHY. This seems like a pretty severe limitation that blocks me from using any of the cool Web API content parsing code in Filters or Handlers.
Is it a technical limitation? Is it trying to keep me from doing a VERY BAD THING(tm) that I'm not seeing?
POSTMORTEM:
I took a look at the source like Filip suggested. ReadAsStreamAsync returns the internal stream and there's nothing stopping you from calling Seek if the stream supports it. In my tests if I called ReadAsAsync then did this:
message.Content.ReadAsStreamAsync().ContinueWith(t => t.Result.Seek(0, SeekOrigin.Begin)).Wait();
The automatic model binding process would work fine when it hit my action method. I didn't use this though, I opted for something more direct:
var buffer = new MemoryStream(_message.Content.ReadAsByteArrayAsync().WaitFor());
var formatters = _message.GetConfiguration().Formatters;
var reader = formatters.FindReader(typeof(Credentials), _message.Content.Headers.ContentType);
var credentials = reader.ReadFromStreamAsync(typeof(Credentials), buffer, _message.Content, null).WaitFor() as Credentials;
With an extension method (I'm in .NET 4.0 with no await keyword)
public static class TaskExtensions
{
public static T WaitFor<T>(this Task<T> task)
{
task.Wait();
if (task.IsCanceled) { throw new ApplicationException(); }
if (task.IsFaulted) { throw task.Exception; }
return task.Result;
}
}
One last catch, HttpContent has a hard-coded max buffer size:
internal const int DefaultMaxBufferSize = 65536;
So if your content is going to be bigger than that you'll need to manually call LoadIntoBufferAsync with a larger size before you try to call ReadAsByteArrayAsync.

The answer you pointed to is not entirely accurate.
You can always read as string (ReadAsStringAsync)or as byte[] (ReadAsByteArrayAsync) as they buffer the request internally.
For example the dummy handler below:
public class MyHandler : DelegatingHandler
{
protected override async System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
var body = await request.Content.ReadAsStringAsync();
//deserialize from string i.e. using JSON.NET
return base.SendAsync(request, cancellationToken);
}
}
Same applies to byte[]:
public class MessageHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var requestMessage = await request.Content.ReadAsByteArrayAsync();
//do something with requestMessage - but you will have to deserialize from byte[]
return base.SendAsync(request, cancellationToken);
}
}
Each will not cause the posted content to be null when it reaches the controller.

I'd put the clientId and the authentication key in the header rather than content.
In which way, you can read them as many times as you like!

Related

How to get input parameters from HttpRequestMessage

I have created a class that inherit from DelegatingHandler and overwrite the SendAsync method. I wanted to validate the request parameters and encode them with AntiXss class before passing it to the main controller. Therefore, I created this. Now when I call the respective controller via SoapUI, I successfully get into the Async method and gets the request object.
Difficulty
I am not able to fetch the request parameters from the HTTPREQUESTMESSAGE object that I passed from the soap ui. Below is the snapshot of the request
CODE
protected override System.Threading.Tasks.Task<System.Net.Http.HttpResponseMessage> SendAsync(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
//Encode here the request object
var requestObj= request.GetQueryNameValuePairs()
.ToDictionary(kv => kv.Key, kv => kv.Value,
StringComparer.OrdinalIgnoreCase);
// work on the request
System.Diagnostics.Trace.WriteLine(request.RequestUri.ToString());
return base.SendAsync(requestObj, cancellationToken)
.ContinueWith(task =>
{
// work on the response
var response = task.Result;
response.Headers.Add("X-Dummy-Header", Guid.NewGuid().ToString());
return response;
});
}
#endregion
}
I just wanted to get the values of the parameters that I passed from the SOAP UI under the object of the HTTPRequestMessage. But not succeeded till now. Any help is appreciated.
After going through certain articles and question I finally got the solution:
var content = request.Content;
string jsonContent = content.ReadAsStringAsync().Result;
The above code worked perfectly

Static HttpClient thread safe on ASP.net HttpRequest

We are creating a wrapper for HttpClient. As we are going to follow performance optimization guidance from https://github.com/mspnp/performance-optimization. We want to avoid anti-pattern - Improper instantiation mentioned in that document. I referred this guidance to my team to use static HttpClient. The feedback I have got is on thread-safety. Each request has a header containing user claim. Since I have a static HttpClient, will it be thread-safe? If we have multiple requests hitting the code (for example GET) at the same time, will it be a race condition to set header? We have implementation as below.
public class HttpClientHelper{
private static readonly HttpClient _HttpClient;
static HttpClientHelper() {
HttpClient = new HttpClient();
HttpClient.Timeout = TimeSpan.FromMinutes(SOME_CONFIG_VALUE);
}
public async Task<HttpResponseMessage> CallHttpClientPostAsync(string requestUri, HttpContent requestBody)
{
AddHttpRequestHeader(httpClient);
var response = await httpClient.PostAsync(requestUri, requestBody); //Potential thread synchronization issue???
return response;
}
public HttpResponseMessage CallHttpClientGet(string requestUri)
{
AddHttpRequestHeader(httpClient);
var response = httpClient.GetAsync(requestUri).Result; //Potential thread synchronization issue???
return response;
}
private void AddHttpRequestHeader(HttpClient client)
{
string HeaderName = "CorrelationId";
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(Properties.Settings.Default.HttpClientAuthHeaderScheme, GetTokenFromClaims()); //Race condition???
if (client.DefaultRequestHeaders.Contains(HeaderName))
client.DefaultRequestHeaders.Remove(HeaderName);
client.DefaultRequestHeaders.Add(HeaderName, Trace.CorrelationManager.ActivityId.ToString());
}
}
Your team is correct, this is far from thread safe. Consider this scenario:
Thread A sets CorrelationId header to "foo".
Thread B sets CorrelationId header to "bar".
Thread A sends request, which contains thread B's CorrelationId.
A better approach would be for your CallXXX methods to create new HttpRequestMessage objects, and set the header on those, and use HttpClient.SendAsync to make the call.
Keep in mind also that re-using HttpClient instances is only beneficial if you're making multiple calls to the same host.

Reading HttpRequestMessage content. DelegatingHandler vs ActionFilter

I've read in a few places that it's impossible to read the contents of the request stream in WebApi more than once, as it's forward only. This presents a problem if you're trying to implement an AOP style request logger and you also want to dump the contents of a request.
I've been playing around with it, and I've noticed that if you try to read the stream in an ActionFilter.OnActionExecutingAsync, as below, you get problems with null content in the controllers action.
public override async System.Threading.Tasks.Task OnActionExecutingAsync(System.Web.Http.Controllers.HttpActionContext actionContext, System.Threading.CancellationToken cancellationToken)
{
var content = await actionContext.Request.Content.ReadAsStringAsync();
traceManager.TraceEvent(TraceEventType.Information, (int)EventId.RequestReceived, "{0}", content);
}
Having said that, if you implement almost the same code in an DelegatingHandler:
protected override async System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
var content = await request.Content.ReadAsStringAsync();
traceManager.TraceEvent(TraceEventType.Information, (int)EventId.RequestReceived, "{0}", content);
return await base.SendAsync(request, cancellationToken);
}
It works flawlessly and doesn't cause any problems with everything further down the pipeline. I understand that the handler runs much sooner than the filter, but why is it possible to do this at one stage of the pipeline but not another?

How to cancel an upload

I have a asp.net web api page where the user can upload some files. I am using jquery-file-upload. Based on some condition, I want to cancel the upload from the server side but it is not working. No matter what I do, the file always goes to the server before asp.net returns the error. Example, I can keep just this when uploading:
public async Task<HttpResponseMessage> Post(int id, CancellationToken token)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, "Cant upload");
}
If I put a breakpoint on the return, I can see that it is hitted as soon as the upload starts but I have to wait the upload to end and only then the javascript error handler gets called. Is it not possible to end the request imediatelly, cancelling the upload?
Update 1:
I replaced jquery-file-upload with jquery-form and now I am using ajaxSubmit on my form. This doen't changed anything.
I also tried to implement a DelegatingHandler, like this:
public class TestErrorHandler : DelegatingHandler
{
protected async override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
//throw new HttpException(403, "You can't upload");
var response = request.CreateResponse(HttpStatusCode.Unauthorized);
response.ReasonPhrase = "You can't upload";
return Task.FromResult<HttpResponseMessage>(response).Result;
}
}
config.MessageHandlers.Add(new TestErrorHandler());
That did not work either.
And I tried to disable buffer on requests:
public class NoBufferPolicySelector : WebHostBufferPolicySelector
{
public override bool UseBufferedInputStream(object hostContext)
{
return false;
}
}
config.Services.Replace(typeof(IHostBufferPolicySelector), new NoBufferPolicySelector());
No game - it still upload all the file before returning the error.
All I need is to cancel a upload request. Is this impossible with web api or I am missing something here?
I had a similar problem, and the only (admittedly ham-fisted) solution I could find to stop the client from uploading the data was to close the TCP connection:
var ctx = Request.Properties["MS_HttpContext"] as HttpContextBase;
if (ctx != null) ctx.Request.Abort();
This works for IIS hosting.

Web API Multipart form-data: Can I save raw request as a file when new request comes in?

For auditing purposes, I would like to store the raw request (as displayed in Fiddler) as a file when a new request comes in before I processing it. Can this be done and how? Thanks!
Yes, you can do it. Following is an example where I use a message handler to log incoming requests. This handler can be used to log any kind of request(not only the multipartform requests).
//add this handler in your config
config.MessageHandlers.Add(new LoggingMessageHandler());
// Logging message handler
public class LoggingMessageHandler : DelegatingHandler
{
private StringBuilder messageBuilder = null;
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
messageBuilder = new StringBuilder();
messageBuilder.AppendFormat("{0} {1}", request.Method.Method, request.RequestUri);
messageBuilder.AppendLine();
//get request headers information
GetHeaders(request.Headers);
//get request content's headers and body
if (request.Content != null)
{
GetHeaders(request.Content.Headers);
// NOTE 1:
// ReadAsStringAsync call buffers the entire request in memory.
// So, even though you could be consuming the request's stream here, since the entire request is buffered
// in memory, you can expect the rest of the call stack to work as expected.
// NOTE 2:
// Look for performance considerations when the request size is too huge.
string body = await request.Content.ReadAsStringAsync();
messageBuilder.AppendLine();
messageBuilder.Append(body);
}
//TODO: log the message here
//logger.Log(messageBuilder.ToString())
// call the rest of the stack as usual
return await base.SendAsync(request, cancellationToken);
}
private void GetHeaders(HttpHeaders headers)
{
foreach (KeyValuePair<string, IEnumerable<string>> header in headers)
{
messageBuilder.AppendLine(string.Format("{0}: {1}", header.Key, string.Join(",", header.Value)));
}
}
}

Resources