Read HttpContent in WebApi controller - c#

How can I read the contents on the PUT request in MVC webApi controller action.
[HttpPut]
public HttpResponseMessage Put(int accountId, Contact contact)
{
var httpContent = Request.Content;
var asyncContent = httpContent.ReadAsStringAsync().Result;
...
I get empty string here :(
What I need to do is: figure out "what properties" were modified/sent in the initial request (meaning that if the Contact object has 10 properties, and I want to update only 2 of them, I send and object with only two properties, something like this:
{
"FirstName": null,
"LastName": null,
"id": 21
}
The expected end result is
List<string> modified_properties = {"FirstName", "LastName"}

By design the body content in ASP.NET Web API is treated as forward-only stream that can be read only once.
The first read in your case is being done when Web API is binding your model, after that the Request.Content will not return anything.
You can remove the contact from your action parameters, get the content and deserialize it manually into object (for example with Json.NET):
[HttpPut]
public HttpResponseMessage Put(int accountId)
{
HttpContent requestContent = Request.Content;
string jsonContent = requestContent.ReadAsStringAsync().Result;
CONTACT contact = JsonConvert.DeserializeObject<CONTACT>(jsonContent);
...
}
That should do the trick (assuming that accountId is URL parameter so it will not be treated as content read).

You can keep your CONTACT parameter with the following approach:
using (var stream = new MemoryStream())
{
var context = (HttpContextBase)Request.Properties["MS_HttpContext"];
context.Request.InputStream.Seek(0, SeekOrigin.Begin);
context.Request.InputStream.CopyTo(stream);
string requestBody = Encoding.UTF8.GetString(stream.ToArray());
}
Returned for me the json representation of my parameter object, so I could use it for exception handling and logging.
Found as accepted answer here

Even though this solution might seem obvious, I just wanted to post it here so the next guy will google it faster.
If you still want to have the model as a parameter in the method, you can create a DelegatingHandler to buffer the content.
internal sealed class BufferizingHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
await request.Content.LoadIntoBufferAsync();
var result = await base.SendAsync(request, cancellationToken);
return result;
}
}
And add it to the global message handlers:
configuration.MessageHandlers.Add(new BufferizingHandler());
This solution is based on the answer by Darrel Miller.
This way all the requests will be buffered.

Related

Returning custom HttpResponseMessage as IActionResult

I have a web api which returns IActionResult.
I return FileContentResult from this api like this
return new FileContentResult(model.Content, ContentType)
{
EnableRangeProcessing = true
};
I have a requirement in which I now want to control StatusCode myself, rather than FileContentResult decide itself.
I don't find any way to do this.
Basically I want to return my own designed HttpResponseMessage in which I can set headers and other stuff myself.
But I don't find any way to do this for IActionResult type.
The only thing that I thought could work is to use ResponseMessageResult something like this
var content = new ByteArrayContent(bytesWithValidationData);
var response = new HttpResponseMessage();
response.Content = content;
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
response.StatusCode = HttpStatusCode.PartialContent;
response.Content.Headers.ContentRange = new ContentRangeHeaderValue(from, to);
return new ResponseMessageResult(response);
But its response is not same as HttpResponse, it just returns json result with HttpResponseMessage object details but does not actually return Http response considering content type etc. where I can download the file.
It gives result like this
Is there any way I can return my designed file result type http response?
Legacy ASP.NET Core web API had special handling for raw HttpResponseMessage instances. ASP.NET Core does not - your controller action has to return an instance of IActionResult.
In your case, I would suggest subclassing FileContentResult and manipulating the status code, then returning your subclass from your controller. Something like the following:
public class MyFileContentResult : FileContentResult
{
public override Task ExecuteResultAsync(ActionContext context)
{
context.HttpContext.Response.StatusCode = <your status code>;
var result = base.ExecuteResultAsync(context);
return result;
}
}

Specifying the content of a HttpRequestMessage for a Web API unit test

I have a web API controller method I want to unit test. It takes in a HttpRequestMessage but I can't work out how to set the content that I want to pass in. Is it possible to create/mock the HttpRequestMessage so that I can give it a string that I want to be the result of await request.Content.ReadAsStringAsync()?
This is my controller method:
[HttpPost]
public async Task<HttpResponseMessage> Post(HttpRequestMessage request)
{
var data = await request.Content.ReadAsStringAsync();
//do something with data
}
I can easily create the HttpRequestMessage with its parameterless constructor, but I can't work out how to set the content to a meaningful value. I would like my test to work along these lines:
[TestMethod]
public async Task PostMethodWorks()
{
var controller = new MyController();
var data = "this will be JSON";
var httpRequestMessage = new HttpRequestMessage();
//set the content somehow so that httpRequestMessage.Content.ReadAsStringAsync returns data
var response = await controller.Post(httpRequestMessage);
//assert something about the response here
}
Is it possible to set the value of the content to some JSON, or will I need to change the method so it takes in a different parameter?
(For more context, the reason I want to have the method taking in a HttpRequestMessage is because I'm working on a legacy codebase with that has loads of controller methods which take in a HttpRequestMessage.)
Is it possible to set the value of the content to some JSON
Yes
You can use any one of the many HttpContent derived classes. Since in this case you want to send JSON content, you would want to use StringContent class
For example
[TestMethod]
public async Task PostMethodWorks() {
//Arrange
var controller = new MyController();
var data = "this will be JSON";
var httpRequestMessage = new HttpRequestMessage();
//set the content somehow so that httpRequestMessage.Content.ReadAsStringAsync returns data
httpRequestMessage.Content = new StringContent(data, Encoding.UTF8, "application/json");
//Act
var response = await controller.Post(httpRequestMessage);
//Assert
//assert something about the response here
}
This however feels like an XY problem as ideally Web API actions don't take HttpRequestMessage as a argument.
or will I need to change the method so it takes in a different parameter?
Model binders exist that can be used to have strongly typed action parameters that will parse the incoming data and populate the models before passing then to the actions.

How do I get the raw request body from the Request.Content object using .net 4 api endpoint

I'm trying to capture the raw request data for accountability and want to pull the request body content out of the Request object.
I've seen suggestions doing a Request.InputStream, but this method is not available on the Request object.
Any idea of how to get a string representation of the Request.Content body?
In your comment on #Kenneth's answer you're saying that ReadAsStringAsync() is returning empty string.
That's because you (or something - like model binder) already read the content, so position of internal stream in Request.Content is on the end.
What you can do is this:
public static string GetRequestBody()
{
var bodyStream = new StreamReader(HttpContext.Current.Request.InputStream);
bodyStream.BaseStream.Seek(0, SeekOrigin.Begin);
var bodyText = bodyStream.ReadToEnd();
return bodyText;
}
You can get the raw data by calling ReadAsStringAsAsync on the Request.Content property.
string result = await Request.Content.ReadAsStringAsync();
There are various overloads if you want it in a byte or in a stream. Since these are async-methods you need to make sure your controller is async:
public async Task<IHttpActionResult> GetSomething()
{
var rawMessage = await Request.Content.ReadAsStringAsync();
// ...
return Ok();
}
EDIT: if you're receiving an empty string from this method, it means something else has already read it. When it does that, it leaves the pointer at the end. An alternative method of doing this is as follows:
public IHttpActionResult GetSomething()
{
var reader = new StreamReader(Request.Body);
reader.BaseStream.Seek(0, SeekOrigin.Begin);
var rawMessage = reader.ReadToEnd();
return Ok();
}
In this case, your endpoint doesn't need to be async (unless you have other async-methods)
For other future users who do not want to make their controllers asynchronous, or cannot access the HttpContext, or are using dotnet core (this answer is the first I found on Google trying to do this), the following worked for me:
[HttpPut("{pathId}/{subPathId}"),
public IActionResult Put(int pathId, int subPathId, [FromBody] myViewModel viewModel)
{
var body = new StreamReader(Request.Body);
//The modelbinder has already read the stream and need to reset the stream index
body.BaseStream.Seek(0, SeekOrigin.Begin);
var requestBody = body.ReadToEnd();
//etc, we use this for an audit trail
}
If you need to both get the raw content from the request, but also need to use a bound model version of it in the controller, you will likely get this exception.
NotSupportedException: Specified method is not supported.
For example, your controller might look like this, leaving you wondering why the solution above doesn't work for you:
public async Task<IActionResult> Index(WebhookRequest request)
{
using var reader = new StreamReader(HttpContext.Request.Body);
// this won't fix your string empty problems
// because exception will be thrown
reader.BaseStream.Seek(0, SeekOrigin.Begin);
var body = await reader.ReadToEndAsync();
// Do stuff
}
You'll need to take your model binding out of the method parameters, and manually bind yourself:
public async Task<IActionResult> Index()
{
using var reader = new StreamReader(HttpContext.Request.Body);
// You shouldn't need this line anymore.
// reader.BaseStream.Seek(0, SeekOrigin.Begin);
// You now have the body string raw
var body = await reader.ReadToEndAsync();
// As well as a bound model
var request = JsonConvert.DeserializeObject<WebhookRequest>(body);
}
It's easy to forget this, and I've solved this issue before in the past, but just now had to relearn the solution. Hopefully my answer here will be a good reminder for myself...
Here's this answer as an extension method:
using System.IO;
using System.Text;
namespace System.Web.Http
{
public static class ApiControllerExtensions
{
public static string GetRequestBody(this ApiController controller)
{
using (var stream = new MemoryStream())
{
var context = (HttpContextBase)controller.Request.Properties["MS_HttpContext"];
context.Request.InputStream.Seek(0, SeekOrigin.Begin);
context.Request.InputStream.CopyTo(stream);
var requestBody = Encoding.UTF8.GetString(stream.ToArray());
return requestBody;
}
}
}
}

Web API 2.2, Is there an easy way to update the HttpRequestMessage's QueryString in a DelegatingHandler?

I've created a custom MessageHandler as such:
public class WebAPICustomMessageHandler : DelegatingHandler {
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
//Parse QueryString
NameValueCollection queryString = HttpUtility.ParseQueryString(request.RequestUri.Query);
if (queryString != null) {
//Find my token
String token = queryString.Get("qsVariable");
if (!String.IsNullOrWhiteSpace(token)) {
//Remove token so it doesn't impact future handlers / controllers / etc.
queryString.Remove("qsVariable");
request.RequestUri.Query = queryString.ToString(); //How can we modify the querystring? apparently it's readonly?
//Append token as custom header to the request
request.Headers.Add("token", new String[] { token });
}
}
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
return response;
}
}
It appears that I can't directly change the QueryString, which is a bit odd as I thought that the entire point of custom message handlers in the Web API pipeline was to allow this exact sort of thing. The ability issue pre and post operations on request/response, including things like data scrubbing / injection etc.
I already know I can do what I want very easily utilizing OWIN (as I've already done it), but now I'm trying to do this without OWIN. Am I going to have to create an entirely new HttpRequestMessage in order to just change the QueryString? From the looks of it, I'll have to build a new Uri, then construct the HttpRequestMessage, then copy over each and every other piece from the original.
Is that the only way to do this? or is there a better way that I'm just not aware of?
Please note, that other routines later in the pipeline are setup to use a token found in the header of the request, but setting the header is not possible if the request came from a submission to an iframe, which is where the process above comes into place. The token is added to the querystring, then converted to a header to prevent changes to the rest of the pipeline.
You're correct, the query string is read-only. Just use a standard redirect with the substituted query string values.
public class WebAPICustomMessageHandler : DelegatingHandler {
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
var query = HttpUtility.ParseQueryString(request.RequestUri.Query);
var token = query["qsVariable"];
// just 'short-circuit' if our token was not found...
if (token == null)
return await base.SendAsync(request, cancellationToken);
token = "newValue"; // modify your value here...
query["token"] = token;
// redirect with new query string...
var response = request.CreateResponse(HttpStatusCode.Redirect);
var uri = request.RequestUri;
var ub = new UriBuilder(uri.Scheme,
uri.Host,
uri.Port,
uri.AbsolutePath);
ub.Query = query.ToString();
response.Headers.Location = ub.Uri;
return response;
}
}
For interest sake, it is possible to modify the query string even though it is read-only but I would avoid this practice.

Get raw post request in an ApiController

I'm trying to implement a Paypal Instant Payment Notification (IPN)
The protocol is
PayPal HTTP POSTs your listener an IPN message that notifies you of an event.
Your listener returns an empty HTTP 200 response to PayPal.
Your listener HTTP POSTs the complete, unaltered message back to
PayPal; the message must contain the same fields (in the same order)
as the original message and be encoded in the same way as the
original message.
PayPal sends a single word back - either VERIFIED (if the message
matches the original) or INVALID (if the message does not match the
original).
So far I have
[Route("IPN")]
[HttpPost]
public void IPN(PaypalIPNBindingModel model)
{
if (!ModelState.IsValid)
{
// if you want to use the PayPal sandbox change this from false to true
string response = GetPayPalResponse(model, true);
if (response == "VERIFIED")
{
}
}
}
string GetPayPalResponse(PaypalIPNBindingModel model, bool useSandbox)
{
string responseState = "INVALID";
// Parse the variables
// Choose whether to use sandbox or live environment
string paypalUrl = useSandbox ? "https://www.sandbox.paypal.com/"
: "https://www.paypal.com/cgi-bin/webscr";
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(paypalUrl);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded"));
//STEP 2 in the paypal protocol
//Send HTTP CODE 200
HttpResponseMessage response = client.PostAsync("cgi-bin/webscr", "").Result;
if (response.IsSuccessStatusCode)
{
//STEP 3
//Send the paypal request back with _notify-validate
model.cmd = "_notify-validate";
response = client.PostAsync("cgi-bin/webscr", THE RAW PAYPAL REQUEST in THE SAME ORDER ).Result;
if(response.IsSuccessStatusCode)
{
responseState = response.Content.ReadAsStringAsync().Result;
}
}
}
return responseState;
}
My problem is I can't figure out how to send the original request to Paypal with the parameters in the same order.
I could build a HttpContent with my PaypalIPNBindingModel but I can't guarantee the order.
Is there any way I could achieve this?
Thank you
I believe you should not use parameter binding and just read the raw request yourself. Subsequently, you can deserialize into the model yourself. Alternatively, if you want to leverage Web API's model binding and at the same time, access the raw request body, here is one way I could think of.
When Web API binds the request body into the parameter, the request body stream is emptied. Subsequently, you cannot read it again.
[HttpPost]
public async Task IPN(PaypalIPNBindingModel model)
{
var body = await Request.Content.ReadAsStringAsync(); // body will be "".
}
So, you have to read the body before model binding runs in Web API pipeline. If you create a message handler, you can ready the body there and store it in the properties dictionary of the request object.
public class MyHandler : DelegatingHandler
{
protected async override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (request.Content != null)
{
string body = await request.Content.ReadAsStringAsync();
request.Properties["body"] = body;
}
return await base.SendAsync(request, cancellationToken);
}
}
Then, from controller you can retrieve the body string, like this. At this point, you have the raw request body as well as the parameter-bound model.
[HttpPost]
public void IPN(PaypalIPNBindingModel model)
{
var body = (string)(Request.Properties["body"]);
}

Categories