Get raw post request in an ApiController - c#

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"]);
}

Related

Having 'the response ended prematurely' exception on client side

There is exception being occurred at client side saying Error while copying the stream content and below that the response ended prematurely.
I cant figure out the solution. In the server side I have asp.net core web api which modify response stream. It actually reads what the controller send and encrypts it to a string and then writes to the response stream.
Also when when the content type is text/plain the response is shown on the Postman but when the content-type is application/json the content is not shown but in the header I can see the content length having some numbers. And for client side, the above exception occurs for both the content-type.
So what i am missing in my middleware code?? I know this is causing issue because when i comment out MyMiddleware in startup.cs, the normal flow works.
Below is the Invoke function in middleware on the server side
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
string reponseContent = string.Empty;
// Store the "pre-modified" response stream.
var existingBody = context.Response.Body;
using (var newBody = new MemoryStream())
{
// We set the response body to our stream so we can read after the chain of middlewares have been called.
context.Response.Body = newBody;
await next(context);
// Set the stream back to the original.
context.Response.Body = existingBody;
newBody.Seek(0, SeekOrigin.Begin);
//reading the content
var contentReader = new StreamReader(newBody);
reponseContent = await contentReader.ReadToEndAsync();
string encryptedData = _cryptoService.Encrypt(reponseContent);
// Send our modified content to the response body.
await context.Response.WriteAsync(encryptedData);
}
I am not sure how you are calling your MiddleWare. I have successfully reproduce the issue and get the response accordingly.
Middle Ware Class:
public class CustomMiddleware
{
private readonly RequestDelegate next;
public CustomMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task InvokeAsync(HttpContext context)
{
string reponseContent = string.Empty;
// Store the "pre-modified" response stream.
var existingBody = context.Response.Body;
using (var newBody = new MemoryStream())
{
// We set the response body to our stream so we can read after the chain of middlewares have been called.
context.Response.Body = newBody;
await next(context);
// Set the stream back to the original.
context.Response.Body = existingBody;
newBody.Seek(0, SeekOrigin.Begin);
//reading the content
var contentReader = new StreamReader(newBody);
reponseContent = await contentReader.ReadToEndAsync();
// string encryptedData = _cryptoService.Encrypt(reponseContent);
// Send our modified content to the response body.
await context.Response.WriteAsync(reponseContent);
}
}
}
Note: You should use constructor to invoke your RequestDelegate like this way. But you have designed this with two parameter, not sure how you are passing the argument while calling.
Startup.cs:
Calling Middleware In Startup.cs under Configure like this way
app.UseMiddleware<CustomMiddleware>();
Request From Postman:
I have tested with simple plain text and application/json type. Sent request to my controller and modify the argument on controller body, and the changed implemented on the middleware.
Controller:
[HttpPost]
public IActionResult MiddlewareReStream([FromBody] Plans plan)
{
plan.PlanName = "New Text";
return Ok(plan);
}
MiddlWare Output:
Note: Notice that I have invoke the request with "PlanName":"Test Plan" and modified the parameter which middleware successfully invoked the changes.
PostMan:
Note: Make sure you have called or implemented the InvokeAsync Middleware accordingly. Because I got the response as you are expecting.
Im thinking that the "Content-Length" header of the response represents the size of the data before you encrypt it and that you need to recalculate the size based on the new encrypted data and reset the header. Im also thinking that different servers are reacting to this missmatch differently, That it might work on IIS but not Kestrel.
In short make sure your "Content-Length" and "Content-Type" headers are matching what you are actually sending.

Though I send response from HttpHead method , client says "Could not get any response"

I have a HttpGet method in a controller in a windows application project.
I need to return Headers from it, so method's return type is HttpResponsemessage. but not able to get any response from that method.
I have one HttpGet method in a controller in a windows application, it is behaving like a Head method, as I am changing Head request type to HttpGet using Handler (ref).
[HttpGet]
public HttpResponseMessage Get(HttpRequestMessage msg)
{
HttpResponseMessage res = new HttpResponseMessage
{
Content = new StringContent("", Encoding.UTF8, "text/plain")
};
res.Headers.Add("Connection", "Keep-Alive");
res.Headers.Add("Keep-Alive", "timeout = 1000 max = 100");
return res;
//return "Hi";
}
In wireshark when I see request and response,
I don't see any response , also tried by sending request from postman,
I am not able to get any response.
but when I change return type to string in gives status code 200 ok in response.
[HttpGet]
public string Get(HttpRequestMessage msg)
{
return "Hi";
}
But I want to return headers not content.
How can I achieve it?
Can anyone help me on this?

HttpClient.PostAsJsonAsync not working or giving any errors in debug mode

I’m having some problems using the Web API with the MVC, not sure what is causing it but it doesn’t throw any exceptions or errors in debug mode, please could someone help to resolve this issue.
Code is as follows:
MVC Controller calls:
PortalLogonCheckParams credParams = new PortalLogonCheckParams() {SecurityLogonLogonId = model.UserName, SecurityLogonPassword = model.Password};
SecurityLogon secureLogon = new SecurityLogon();
var result = secureLogon.checkCredentials(credParams);
Data Access Object method:
public async Task <IEnumerable<PortalLogon>> checkCredentials(PortalLogonCheckParams credParams)
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:50793/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// Check Credentials
//Following call fails
HttpResponseMessage response = await client.PostAsJsonAsync("api/chkPortalLogin", credParams);
if (response.IsSuccessStatusCode)
{
IEnumerable<PortalLogon> logonList = await response.Content.ReadAsAsync<IEnumerable<PortalLogon>>();
return logonList;
}
else return null;
}
}
Web API:
[HttpPost]
public IHttpActionResult chkPortalLogin([FromBody] PortalLogonCheckParams LogonParams)
{
List<Mod_chkPortalSecurityLogon> securityLogon = null;
String strDBName = "";
//Set the database identifier
strDBName = "Mod";
//Retrieve the Logon object
using (DataConnection connection = new DataConnection(strDBName))
{
//Retrieve the list object
securityLogon = new Persistant_Mod_chkPortalSecurityLogon().findBy_Search(connection.Connection, LogonParams.SecurityLogonLogonId, LogonParams.SecurityLogonPassword);
}
AutoMapper.Mapper.CreateMap<Mod_chkPortalSecurityLogon, PortalLogon>();
IEnumerable<PortalLogon> securityLogonNew = AutoMapper.Mapper.Map<IEnumerable<Mod_chkPortalSecurityLogon>, IEnumerable<PortalLogon>>(securityLogon);
return Ok(securityLogonNew);
}
You need to remove the [FromBody] attribute from the parameter
Using
[FromBody]
To force Web API to read a simple type from the request body, add the
[FromBody] attribute to the parameter:
public HttpResponseMessage Post([FromBody] string name) { ... }
In this example, Web API will use a media-type formatter to read the
value of name from the request body. Here is an example client
request.
POST http://localhost:5076/api/values HTTP/1.1
User-Agent: Fiddler
Host: localhost:5076
Content-Type: application/json
Content-Length: 7
"Alice"
When a parameter has [FromBody], Web API uses the Content-Type header
to select a formatter. In this example, the content type is
"application/json" and the request body is a raw JSON string (not a
JSON object).
At most one parameter is allowed to read from the message body.

Sending JSON from WebApi to Android

I have problem understanding sending Json from WebApi to Android. I know how to make requests from Json, but don't know how to make Task that waits for request and return Json. I have json made from Mongo DB query with JsonConvert.SerializeObject.
I have been searching and found this that I have for now, but this isn't what I need I think, I don't have url in PostAsync, I just want to wait for request and respond with Json, where I need to look for:
public async Task<HttpStatusCode> SendAsync()
{
var content = new StringContent(
jsonString,
Encoding.UTF8,
"application/x-www-form-urlencoded");
HttpResponseMessage response = await _httpClient.PostAsync(_url, content);
return response.StatusCode;
}
You need to return an IHttpActionResult implementation. For example:
public async Task<IHttpActionResult> SendAsync()
{
// Object passed to Ok will be automatically serialized to JSON
// if response content type is JSON (and, by default, this is the serialization
// format in ASP.NET WebAPI)
return Ok(new { text = "hello world" });
}

How to forward an HttpRequestMessage to another server

What's the best way to forward an http web api request to another server?
Here's what I'm trying:
I have a .NET project where when I get certain API requests I want to modify the request, forward it to another server, and return the response sent by that second server.
I'm doing the following:
[Route("forward/{*api}")]
public HttpResponseMessage GetRequest(HttpRequestMessage request)
{
string redirectUri = "http://productsapi.azurewebsites.net/api/products/2";
HttpRequestMessage forwardRequest = request.Clone(redirectUri);
HttpClient client = new HttpClient();
Task<HttpResponseMessage> response = client.SendAsync(forwardRequest);
Task.WaitAll(new Task[] { response } );
HttpResponseMessage result = response.Result;
return result;
}
Where the Clone method is defined as:
public static HttpRequestMessage Clone(this HttpRequestMessage req, string newUri)
{
HttpRequestMessage clone = new HttpRequestMessage(req.Method, newUri);
if (req.Method != HttpMethod.Get)
{
clone.Content = req.Content;
}
clone.Version = req.Version;
foreach (KeyValuePair<string, object> prop in req.Properties)
{
clone.Properties.Add(prop);
}
foreach (KeyValuePair<string, IEnumerable<string>> header in req.Headers)
{
clone.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
return clone;
}
However, for some reason instead of redirecting the url to the specified redirectUri I get a 404 response where the RequestMessage.RequestUri is set to http://localhost:61833/api/products/2. (http://localhost:61833 is the root of the original request uri).
Thanks
You might need to explicitly set the host header on the clone instance. Otherwise you are just copying the original request's host header value across to the clone.
i.e. add the following line to the end of your Clone method:
clone.Headers.Host = new Uri(newUri).Authority;
Also, depending on what you are trying to achieve here, you may also need to handle other issues like cookie domains on the request not matching the new domain you are forwarding to as well as setting the correct domain on any response cookies that are returned.

Categories