14

I'm trying to extract some data out of a request in the new Asp.Net Web Api. I have a handler setup like this:

public class MyTestHandler : DelegatingHandler { protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { if (request.Content.IsFormData()) { request.Content.ReadAsStreamAsync().ContinueWith(x => { var result = ""; using (var sr = new StreamReader(x.Result)) { result = sr.ReadToEnd(); } Console.Write(result); }); } return base.SendAsync(request, cancellationToken); } } 

This is my http request:

POST http://127.0.0.1/test HTTP/1.1 Connection: Keep-Alive Content-Length: 29 Content-Type: application/x-www-form-urlencoded Expect: 100-continue Host: 127.0.0.1 my_property=my_value 

the problem is that no matter how I try to read the info from request.Content it's always empty. I've tried

request.Content.ReadAsStreamAsync request.Content.ReadAsFormDataAsync request.Content.ReadAs<FormDataCollection> 

as well as

 [HttpGet,HttpPost] public string Index([FromBody]string my_property) { //my_property == null return "Test"; } 

None if it works. I cannot get the data out of the body. I'm hosting inside IIS on Windows 7 and using Fiddler to submit the request. What am I doing wrong?

10
  • It would be helpful to know if an independant client akin to wcftestclient.exe produces similar results. Commented Aug 17, 2012 at 14:31
  • Tried to run that but it wont because it says my endpoint does not have any metadata. Commented Aug 17, 2012 at 14:43
  • That looks mighty strange. Is there some reason you are choosing this method of data transfer over the provided [HttpGet] and [HttpPost] methodology ? Commented Aug 17, 2012 at 14:46
  • @Micah - Have you tried wcf.codeplex.com/… Commented Aug 17, 2012 at 14:50
  • @Dan-o Yes. I'm trying to see if there's an apikey included in the body of every request and validating it. If not then I want to throw an error. In this case the ApiKey can either be included in the querystring, a header, or the body of the request. Commented Aug 17, 2012 at 14:53

6 Answers 6

20

The problem is that with the Web Api the body can only be read once. I had an HTTP module running that was logging all the details of the request and was reading through the body.

Sign up to request clarification or add additional context in comments.

2 Comments

so, doing this sort of logging in not possible, right ??
It is possible, you need to read it into a buffer first, using the LoadIntoBufferAsync() method first. This makes the content still available for the base method. More from here weblogs.asp.net/fredriknormen/…
7

It's ugly, but you it seems from initial tinkering that you can, in fact, replace the Content in DelegatingHandler ...

protected override Task SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { Stream stream = new MemoryStream(); request.Content.ReadAsStreamAsync().Result.CopyTo(stream); stream.Seek(0,SeekOrigin.Begin); // copy off the content "for later" string query = new StreamReader(stream).ReadToEnd(); stream.Seek(0,SeekOrigin.Begin); // if further processing depends on content type // go ahead and grab current value var contentType = request.Content.Headers.ContentType; request.Content = new StreamContent(stream); request.Content.Headers.ContentType = contentType; return base.SendAsync(request, cancellationToken); } 

I have no idea if this is good form or bad (suspect bad), but .... it seems to work and follows model I've seen recommended for those that need to modify request headers and content "on the way in" with a DelegatingHandler.

Your mileage may vary substantially.

Comments

5

I based my answer on brmore's code;

This function can safe read content in any handler

private string SafeReadContentFrom(HttpRequestMessage request) { var contentType = request.Content.Headers.ContentType; var contentInString = request.Content.ReadAsStringAsync().Result; request.Content = new StringContent(contentInString); request.Content.Headers.ContentType = contentType; return contentInString; } 

Comments

2

This works for me.

[HttpPost] public IHttpActionResult Index(HttpRequestMessage request) { var form = request.Content.ReadAsFormDataAsync().Result; return Ok(); } 

1 Comment

I have not tried this, so can't say from my own experience but still wanted to add a quick comment pointing to another answer: stackoverflow.com/a/17971766/2838827 It seems using this without async await could cause your application to deadlock. Use with caution.
0

I had the same issue and finally chose not to write content in the logs. I am living with logging Content-Type and Content-Length.

But it is always a good idea to write all the content in the logs as far as possible.

But seems like with WebApi presently we cannot achieve this.

Comments

0

You can create a provider first. MultipartMemoryStreamProvider() then Request.Content.ReadAsMultipartAsync(provider); then read the content

public async Task<IHttpActionResult> Post(int id, string type) { // Check if the request contains multipart/form-data. if(!Request.Content.IsMimeMultipartContent("form-data")) return BadRequest("Unsupported media type"); try { var azureManager = new AzureManager(); var imageManager = new ImageManager(); var provider = new MultipartMemoryStreamProvider(); await Request.Content.ReadAsMultipartAsync(provider); var assets = new List<Asset>(); foreach (var file in provider.Contents) { var stream = await file.ReadAsStreamAsync(); var guid = Guid.NewGuid(); string blobName = guid.ToString(); await azureManager.UploadAsync(blobName, stream); var asset = new Asset { PropertyId = id, FileId = guid, FileName = file.Headers.ContentDisposition.FileName.Trim('\"').ToLower(), FileSize = file.Headers.ContentLength ?? 0, MimeType = file.Headers.ContentType.MediaType.ToLower() }; if (type == "photos") { asset.Type = AssetType.Photo; // Resize and crop copies to 16:9 using (MemoryStream thumb = imageManager.ResizeImage(stream, 320, 180)) { await azureManager.UploadAsync(blobName, thumb, BlobContainers.Thumbs); } using (MemoryStream photo = imageManager.ResizeImage(stream, 1024, 576)) { await azureManager.UploadAsync(blobName, photo, BlobContainers.Photos); } } else asset.AssumeType(); assets.Add(asset); } db.Assets.AddRange(assets); await db.SaveChangesAsync(); return Ok(new { Message = "Assets uploaded ok", Assets = assets }); } catch (Exception ex) { return BadRequest(ex.GetBaseException().Message); } } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.