VJAI
VJAI

Reputation: 32768

Calling ReadAsFormDataAsync twice

I'm facing a situation where I've to read the form data from incoming request in ASP.NET Web API twice (from model binder and filter). I've tried using LoadIntoBufferAsync but no luck.

// from model binder
Request.Content.LoadIntoBufferAsync().Wait();
var formData = Request.Content.ReadAsFormDataAsync().Result;

// from filter
var formData = Request.Content.ReadAsFormDataAsync().Result;

Upvotes: 5

Views: 3917

Answers (3)

David McEnderfer
David McEnderfer

Reputation: 11

During the development of a REST API, we had a need to authenticate a request prior to allowing the response to be processed within the controller, and so this created a need to be able to read the header as well as the form (if any) to determine if the credentials were passed into the request within the body of the form rather than through the request header.

A few lines of code reset the stream pointer to the beginning of the stream so that MVC would be able to read the form and populate the view model in the controller

    public class WebServiceAuthenticationAttribute : AuthorizationFilterAttribute
    {
        public override void OnAuthorization(HttpActionContext actionContext)
        {
            var authenticationHeaderValue = actionContext.Request.Headers.Authorization;

            try
            {
                if (authenticationHeaderValue != null)
                {
                    var webRequestInfo = new WebRequestInfo(actionContext.Request.Method, actionContext.Request.RequestUri);
                    this.AuthenticationHeaderService.LogOnUsingAuthenticationHeader(authenticationHeaderValue, webRequestInfo);
                }
                else if (actionContext.Request.Content.IsFormData())
                {
                    Task<NameValueCollection> formVals = actionContext.Request.Content.ReadAsFormDataAsync();
                    this.AuthenticationFormService.LogOnUsingFormsAuthentication(formVals.Result);

                    // reset the underlying stream to the beginning so that others may use it in the future...
                    using (var s = new System.IO.MemoryStream())
                    {
                        var ctx = (HttpContextBase) actionContext.Request.Properties["MS_HttpContext"];
                        ctx.Request.InputStream.Seek(0, System.IO.SeekOrigin.Begin);
                    }
                }
            }
            catch (Exception)
            {
                throw;
            }
        }
    }

Initially the data model was not being created by MVC and a null was passed into the controller method. After resetting the stream, MVC was able to read the form, create and populate the data model, and pass it into the controller method.

[WebServiceAuthentication]
public HttpResponseMessage Get(DocumentRequestModel requestForm)
{
    var response = CreateResponse(HttpStatusCode.OK);
    response.Content = new ByteArrayContent(this.documentService.GetDocument(requestForm.DocumentId.ToString()));
    response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
    return response;
}

Upvotes: 1

Aliostad
Aliostad

Reputation: 81680

You really should not need to do that. At the end of the day, HttpContext Stream points to the same stream Web API reads from.

You can try to put LoadIntoBufferAsync in both places as one could trigger before the other and it was already in the buffer, calling LoadIntoBufferAsync has no side effect.

// from model binder
Request.Content.LoadIntoBufferAsync().Wait();
var formData = Request.Content.ReadAsFormDataAsync().Result;

// from filter
Request.Content.LoadIntoBufferAsync().Wait();
var formData = Request.Content.ReadAsFormDataAsync().Result;

Upvotes: 0

AlexGad
AlexGad

Reputation: 6692

The problem is that the underlying buffer for content is a forward-only stream that can only be read once.

Why do you need to read it twice? A little more context would help. Is it that you are reading from two separate filters?

EDIT: might try reading directly from MS_HttpContext and using that as your content stream (don't think this works in a self hosted environment).

using (var s = new System.IO.MemoryStream()) {   
  var ctx = (HttpContextBase)actionContext.Request.Properties["MS_HttpContext"];  
  ctx.Request.InputStream.Seek(0, System.IO.SeekOrigin.Begin);  
  ctx.Request.InputStream.CopyTo(s);   var body =
  System.Text.Encoding.UTF8.GetString(s.ToArray()); 
}

Upvotes: 3

Related Questions