POST throws HttpRequestMessage does not contain a definition for Form - c#

I am trying to get POST data in C# and everything I have read says to use
Request.Form["parameterNameHere"]
I am trying that, but I get an error saying
System.Net.Http.HttpRequestMessage does not contain a definition for Form and no extension method for Form.'
The method in question is
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Web.HttpRequest;
namespace TextServer.Controllers
{
public class TextController : ApiController
{
// POST api/<controller>
public HttpResponseMessage Post([FromBody]string value)
{
string val = Request.Form["test"];
HttpResponseMessage response = new HttpResponseMessage();
response.Content = new StringContent("Your message to me was: " + value);
return response;
}
Any help is greatly appreciated.

You should pass your object in the request body and retrieve values from the body:
public HttpResponseMessage Post([FromBody] SomeModel model)
{
var value = model.SomeValue;
...
Or if all you need is the string:
public HttpResponseMessage Post([FromBody] string value)
{
HttpResponseMessage response = new HttpResponseMessage();
response.Content = new StringContent("Your message to me was: " + value);
return response;
}

Related

How to call external api with Azure function with POST method with request body data

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System.Net;
using System.IO;
namespace FunctionRestApi
{
public static class Function1
{
[FunctionName("Function1")]
public static IActionResult Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
var httpWebRequest = (HttpWebRequest)WebRequest.Create("URL_with_client_id_authorization_token");
httpWebRequest.ContentType = "application/json";
httpWebRequest.Method = "GET";
var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
{
var result = streamReader.ReadToEnd();
log.LogInformation(result);
}
return new OkObjectResult(value: httpWebRequest);
}
}
}
I am new to azure function. This code works when I am just using 'GET' method. But if I want to use 'POST' method with request body data i.e. date range (start_date and end_date) and some other sub_user_id, then how can I do that?
Here is a simple example of a POST request using HttpClient with some comments:
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
namespace AzureFunctionsSandbox.Functions
{
public static class Function1
{
private static readonly HttpClient _httpClient = new HttpClient();
[FunctionName("Function1")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest request,
ILogger log)
{
// create request as an object to easily set values
var myRequest = new MyRequest
{
StartDate = DateTime.Now,
EndDate = DateTime.Now.AddDays(1),
SubUserId = "ABC123"
};
// serialize to JSON string for POST request body
var myRequestJsonBody = JsonConvert.SerializeObject(myRequest);
// .PostAsync() requires a HttpContent object - StringContent is a sub class
var requestContent = new StringContent(myRequestJsonBody, Encoding.UTF8, "application/json");
// make the POST request
var response = await _httpClient.PostAsync("URL_with_client_id_authorization_token", requestContent);
// use response body for further work if needed...
var responseBody = response.Content.ReadAsStringAsync();
return new OkResult();
}
}
public class MyRequest
{
[JsonProperty(PropertyName = "start_date")]
public DateTime StartDate { get; set; }
[JsonProperty(PropertyName = "end_date")]
public DateTime EndDate { get; set; }
[JsonProperty(PropertyName = "sub_user_id")]
public string SubUserId { get; set; }
}
}
https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=net-5.0
See one of the answers in this SO question, it shows how to make a POST call using the HttpClient class, however it is creating new instance of it, it is not the right way. As a best practice use only a static object of HttpClient in your function app.
first check with POSTMAN how you can connect to external API with what authentication configuration BASIC/ BEARER then you can write code using same configuration

HttpClient Post Call

i want to do a REST POST call, using HttpClient.I keep having error - 'RestCall.PostRestCall(string, string, string, string)': not all code paths return a value'
Below is my Code. Its meant to receive baseUrl, contentType, requestBody, httpMethod; as input parameter and return the response status code, status description and response content.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
namespace RestPostCall
{
public class RestCall
{
public static string PostRestCall(string baseURL, string httpMethod, string contentType, string requestBody)
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(baseURL);
// Add an Accept header for JSON format.
client.DefaultRequestHeaders.Accept.Add(new
MediaTypeWithQualityHeaderValue(contentType));
// List data response.
HttpResponseMessage response = client.GetAsync(requestBody).Result;
if (response.IsSuccessStatusCode)
{
// Parse the response body.
var dataObjects = response.Content.ReadAsAsync<IEnumerable<IDataObject>>().Result;
//Make sure to add a reference to System.Net.Http.Formatting.dll
}
else
{
Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase);
}
//Make any other calls using HttpClient here.
//Dispose once all HttpClient calls are complete.
client.Dispose();
}
}
}
You method does not return any value, so change signature to return void type:
public static void PostRestCall(string baseURL, string httpMethod, string contentType,
string requestBody)
Change your method return type to void or if you want to return any value use return statement.

Compare the Hash of a JSON object

I am attempting to get a hash comparer to work so I can validate an incoming request.
Flow:
Sender creates json object -> sender creates hash of json object with a key that they and I know -> sender sends json object and header with hash in it -> I recieve request -> I hash the json object with the common key -> I compare my hash to the one in the header to validate user sending it
I am struggling to create a hash from my json object.
This is the example code in Ruby (from the sender) where request_payload is JSON object.
hmac=OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha1'),YOUR_COMPANY_SIGNING_KEY,request_payload)
signature = Base64.strict_encode64(hmac)
I want to do this in C#.
I am using the data from the Call Rail API (see right side) and attempting to hash it into string and then encode it.
[HttpPost]
public async Task<ActionResult> PostAsync(dynamic request)
{
string signature = GetHash(request.ToString(), "072e77e426f92738a72fe23c4d1953b4"); // this key is from the example in Call Rail
string encodedSignature = Base64Encode(signature);
return Ok();
}
public static String GetHash(dynamic text, String key)
{
ASCIIEncoding encoding = new ASCIIEncoding();
Byte[] textBytes = encoding.GetBytes(text);
Byte[] keyBytes = encoding.GetBytes(key);
Byte[] hashBytes;
using (HMACSHA1 hash = new HMACSHA1(keyBytes))
hashBytes = hash.ComputeHash(textBytes);
return BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
}
public static string Base64Encode(string plainText)
{
var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
return System.Convert.ToBase64String(plainTextBytes);
}
I think where I am struggling is how I can take my incoming JSON
{"answered":false,"business_phone_number":"","call_type":"voicemail","company_id":155920786,"company_name":"Boost Marketing","company_time_zone":"America/Los_Angeles","created_at":"2018-02-19T13:41:00.252-05:00","customer_city":"Rochester","customer_country":"US","customer_name":"Kaylah Mills","customer_phone_number":"+12148654559","customer_state":"PA","device_type":"","direction":"inbound","duration":"13","first_call":false,"formatted_call_type":"Voicemail","formatted_customer_location":"Rochester, PA","formatted_business_phone_number":"","formatted_customer_name":"Kaylah Mills","prior_calls":16,"formatted_customer_name_or_phone_number":"Kaylah Mills","formatted_customer_phone_number":"214-865-4559","formatted_duration":"13s","formatted_tracking_phone_number":"404-555-8514","formatted_tracking_source":"Google Paid","formatted_value":"--","good_lead_call_id":715587840,"good_lead_call_time":"2016-06-17T10:23:33.363-04:00","id":766970532,"lead_status":"previously_marked_good_lead","note":"","recording":"https://app.callrail.com/calls/766970532/recording/redirect?access_key=aaaaccccddddeeee","recording_duration":8,"source_name":"Google AdWords","start_time":"2018-02-19T13:41:00.236-05:00","tags":[],"total_calls":17,"tracking_phone_number":"+14045558514","transcription":"","value":"","voicemail":true,"tracker_id":354024023,"keywords":"","medium":"","referring_url":"","landing_page_url":"","last_requested_url":"","referrer_domain":"","conversational_transcript":"","utm_source":"google","utm_medium":"cpc","utm_term":"","utm_content":"","utm_campaign":"Google AdWords","utma":"","utmb":"","utmc":"","utmv":"","utmz":"","ga":"","gclid":"","integration_data":[{"integration":"Webhooks","data":null}],"keywords_spotted":"","recording_player":"https://app.callrail.com/calls/766970532/recording?access_key=aaaabbbbccccdddd","speaker_percent":"","call_highlights":[],"callercity":"Rochester","callercountry":"US","callername":"Kaylah Mills","callernum":"+12148654559","callerstate":"PA","callsource":"google_paid","campaign":"","custom":"","datetime":"2018-02-19 18:41:00","destinationnum":"","ip":"","kissmetrics_id":"","landingpage":"","referrer":"","referrermedium":"","score":1,"tag":"","trackingnum":"+14045558514","timestamp":"2018-02-19T13:41:00.236-05:00"}
And then be able to Hash it into something useful.
With the test signing key I am given, I should get back UZAHbUdfm3GqL7qzilGozGzWV64=. I know this from the APIDocs.
I am currently sending up the JSON string above via postman but I notice that extra '{ }' are added on when I treat it as datatype dynamic or object.
Any insight would be greatly appreciated!
I think the problem you are facing is that the .NET Core WebAPI is helpfully parsing the body into JSON (into a JObject) for you.
As #dbc identified, really what you need is the raw string body to use to generate the HMAC Signature, which you can verify before parsing the body into JSON yourself.
I tested This answer and was able to receive the body as a plain string:
using System;
using System.Linq;
using System.Threading.Tasks;
using System.IO;
using Microsoft.AspNetCore.Mvc.Formatters;
namespace netcoretest {
public class RawJsonBodyInputFormatter : InputFormatter
{
public RawJsonBodyInputFormatter()
{
this.SupportedMediaTypes.Add("application/json");
}
public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
var request = context.HttpContext.Request;
using (var reader = new StreamReader(request.Body))
{
var content = await reader.ReadToEndAsync();
return await InputFormatterResult.SuccessAsync(content);
}
}
protected override bool CanReadType(Type type)
{
return type == typeof(string);
}
}
}
in Startup.cs:
// in ConfigureServices()
services.AddMvc(options => {
options.InputFormatters.Insert(0, new RawJsonBodyInputFormatter());
});
In your controller:
[HttpPost]
public async Task<ActionResult> PostTest([FromBody]string request)
{
// here request is now the request body as a plain string;
// you can now compute the signature on it and then later parse it to JSON.
}
However, testing your current code to generate the Base64-encoded signature, I am not getting the correct signature:
you are converting the output of the HMAC to hexadecimal string, and then taking the bytes of that string and putting those into your Base64 encoding. The sample ruby you linked returns plain bytes, not a hexadecimal string from HMAC.digest:
[5] pry(main)> hmac = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha1'), YOUR_COMPANY_SIGNING_KEY, s)
=> "Q\x90\amG_\x9Bq\xAA/\xBA\xB3\x8AQ\xA8\xCCl\xD6W\xAE"
So at least that portion needs to be corrected in your implementation as well.
Update
I was able to get the correct signature with the following code:
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System;
using System.Security.Cryptography;
using System.Text;
namespace netcoretest.Controllers
{
[Route("test")]
[ApiController]
public class TestController : ControllerBase
{
public TestController()
{
}
// POST: /test
[HttpPost]
public async Task<ActionResult> PostTest([FromBody]string request)
{
ASCIIEncoding encoding = new ASCIIEncoding();
Byte[] key = encoding.GetBytes("072e77e426f92738a72fe23c4d1953b4");
HMACSHA1 hmac = new HMACSHA1(key);
Byte[] bytes = hmac.ComputeHash(encoding.GetBytes(request));
Console.WriteLine(ByteArrayToString(bytes));
String result = System.Convert.ToBase64String(bytes);
Console.WriteLine(result);
return Ok();
}
public static string ByteArrayToString(byte[] ba)
{
return BitConverter.ToString(ba).Replace("-","");
}
}
}
I tested posting to this endpoint using the following:
url --request POST https://localhost:5001/test --insecure --header 'Content-Type: application/json' --data-binary #test.json
where test.json is the sample JSON blob from the API Documentation.
If using this same code you cannot get the signature to match, double check that your test.json does not have any trailing newlines or whitespace.
Hope this helps!
Though #john_Ledbetter 's answer works great, I decided an action filter fit me better. So I used his answer as a base and modified it for me. I don't believe this solution would need the InputFormatter
ValidateCallRailRequestFiler.cs
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Internal;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace My_API.ActionFilters
{
public class ValidateCallRailRequestFilter: ActionFilterAttribute
{
//private readonly ILogger<ValidateCallRailRequestFilter> _logger;
//public ValidateCallRailRequestFilter(ILogger<ValidateCallRailRequestFilter> logger)
//{
// _logger = logger;
//}
public override void OnActionExecuting(ActionExecutingContext actionContext)
{
//executing before action is called
// this should only return one object since that is all an API allows. Also, it should send something else it will be a bad request
var param = actionContext.ActionArguments.SingleOrDefault();
if (param.Value == null)
{
//_logger.LogError("Object sent was null. Caught in ValidateCallRailRequestFilter class.");
actionContext.Result = new BadRequestObjectResult("Object sent is null");
return;
}
var context = actionContext.HttpContext;
if (!IsValidRequest(context.Request))
{
actionContext.Result = new ForbidResult();
return;
}
base.OnActionExecuting(actionContext);
}
private static bool IsValidRequest(HttpRequest request)
{
string json = GetRawBodyString(request.HttpContext);
string token = "072e77e426f92738a72fe23c4d1953b4"; // this is the token that the API (Call Rail) would provide
string signature = request.Headers["Signature"];
// validation for comparing encoding to bytes and hashing to be the same
//https://rextester.com/EBR67249
ASCIIEncoding encoding = new ASCIIEncoding();
byte[] key = encoding.GetBytes(token);
HMACSHA1 hmac = new HMACSHA1(key);
byte[] bytes = hmac.ComputeHash(encoding.GetBytes(json));
string result = System.Convert.ToBase64String(bytes);
return signature.Equals(result, StringComparison.OrdinalIgnoreCase);
}
public static string GetRawBodyString(HttpContext httpContext)
{
var body = "";
if (httpContext.Request.ContentLength == null || !(httpContext.Request.ContentLength > 0) ||
!httpContext.Request.Body.CanSeek) return body;
httpContext.Request.EnableRewind();
httpContext.Request.Body.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(httpContext.Request.Body, System.Text.Encoding.UTF8, true, 1024, true))
{
body = reader.ReadToEnd();
}
httpContext.Request.Body.Position = 0;
return body;
}
}
}
This includes a body reader that reads in the JSON and a way to deny incoming requests that don't match the signature as 403 forbidden.
Then within my controller:
[ValidateCallRailRequestFilter]
[HttpPost]
public async Task<ActionResult> PostAsync(dynamic request)
{
...
return Ok();
}
with a using My_Api.ActionFilters;
And then I hit it with postman.

Get body text from an HttpWebRequest object?

I have a (legacy) method in my codebase which generates and returns a ready-to-send HTTP POST message as a System.Net.HttpWebRequest object:
public HttpWebRequest GetHttpWebRequest(string body, string url, string contentType)
{
HttpWebRequest request = HttpWebRequest.CreateHttp(url);
request.Method = "POST";
// (More setup stuff here...)
using (var writer = new StreamWriter(request.GetRequestStream()))
{
writer.Write(body);
}
return request;
}
I'd like to write a unit test which verifies that the HttpWebRequest instance returned by this method actually does have the message body text that was passed in to the method in the body parameter.
Question: How can I get the body text of an HttpWebRequest object (without ever actually sending the HTTP request)?
Stuff I've tried so far:
new StreamReader(myHttpWebRequest.GetRequestStream()).ReadToEnd() - Fails at runtime with ArgumentException: Stream was not readable.
The HttpWebRequest class doesn't seem to have any property that would allow getting/reading the HTTP message body such as Body, Message, Text, etc.
I would write a http listener and make real http requests.
Here is a sample server using WCF + the client code. Just call await TestClient.Test(); (You can also test the server with a browser like http://localhost:8088/TestServer/Dummy)
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Web;
using System.Text;
using System.Threading.Tasks;
namespace SO
{
[ServiceContract]
public class TestServer
{
static WebServiceHost _host = null;
public static Task Start()
{
var tcs = new TaskCompletionSource<object>();
try
{
_host = new WebServiceHost(typeof(TestServer), new Uri("http://0.0.0.0:8088/TestServer"));
_host.Opened += (s, e) => { tcs.TrySetResult(null); };
_host.Open();
}
catch(Exception ex)
{
tcs.TrySetException(ex);
}
return tcs.Task;
}
//A method that accepts anything :)
[OperationContract, WebInvoke(Method = "*", UriTemplate ="*")]
public Message TestMethod(Stream stream )
{
var ctx = WebOperationContext.Current;
var request = ctx.IncomingRequest.UriTemplateMatch.RequestUri.ToString();
var body = new StreamReader(stream).ReadToEnd();
Console.WriteLine($"{ctx.IncomingRequest.Method} {request}{Environment.NewLine}{ctx.IncomingRequest.Headers.ToString()}BODY:{Environment.NewLine}{body}");
return ctx.CreateTextResponse( JsonConvert.SerializeObject( new { status = "OK", data= "anything" }), "application/json", Encoding.UTF8);
}
}
public class TestClient
{
public static async Task Test()
{
await TestServer.Start();
var client = new HttpClient();
var objToSend = new { name = "L", surname = "B" };
var content = new StringContent( JsonConvert.SerializeObject(objToSend) );
var response = await client.PostAsync("http://localhost:8088/TestServer/TestMethod?aaa=1&bbb=2", content);
Console.WriteLine(response.StatusCode);
Console.WriteLine(await response.Content.ReadAsStringAsync());
}
}
}

Invalid ModelState, HTTP 400 Bad Request for JSON Array Sent to C# Web API Controller

In a C# Web API, I am trying to accept a POST request that contains a JSON array. I want to deserialize the JSON array to a LIST or ILIST of RegisterBindingModel class objects. Then, in the controller actions, I will iterate over the list and perform the desired action.
I am using essentially the stock ASP.NET 5 Web Application template in Visual Studio 2015. I have added a RegisterList method on the Account controller.
Separately, I have created a Web client in a C# console application. The client sends a POST request that contains a JSON array.
The response I get is always 400 - Bad Request.
Am I supposed to deserialize the JSON array to an ILIST or LIST in the RegisterList method signature? I've tried to use JsonConverter.DeserializeObject, but IntelliSenese says that the type name DeserializeObject does not exist in type JsonConverter.
The API documentation that generates with the Visual Studio template indicates that the client's JSON array is formatted correctly.
The following is the code for the RegisterList method:
// POST api/Account/RegisterList
[AllowAnonymous]
[Route("RegisterList")]
public async Task<IHttpActionResult> RegisterList(List<RegisterBindingModel> modelList)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
foreach (RegisterBindingModel model in modelList)
{
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
}
return Ok();
}
The following is the code for the client:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Http;
using System.Net.Http.Headers;
using Web_Client_Register_Account;
using Newtonsoft.Json;
namespace Web_Client_Register_Account
{
class Program
{
static void Main(string[] args)
{
RunAsync().Wait();
}
static async Task RunAsync()
{
using (var client = new HttpClient())
{
Console.WriteLine("Hit any key");
Console.ReadLine();
client.BaseAddress = new Uri("http://localhost:9000/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var registrations = new List<Registration> { new Registration { Email = "orc#coast2coast.net", Password = "Secrets1!", ConfirmPassword = "Secrets1!" }, new Registration { Email = "gargoyle#coast2coast.net", Password = "Secrets1!", ConfirmPassword = "Secrets1!" }, new Registration { Email = "elf#coast2coast.net", Password = "Secrets1!", ConfirmPassword = "Secrets1!" }, new Registration { Email = "ranger#coast2coast.net", Password = "Secrets1!", ConfirmPassword = "Secrets1!" } };
//HTTP Post - A JSON List in a Single POST
var registration_manifest = JsonConvert.SerializeObject(registrations);
Console.ReadLine();
HttpResponseMessage response = await client.PostAsJsonAsync("api/Account/RegisterList", registration_manifest);
if (response.IsSuccessStatusCode)
{
Uri registrantUrl = response.Headers.Location;
Console.WriteLine(registrantUrl);
Console.ReadLine();
}
Console.WriteLine(response);
Console.ReadLine();
}
}
}
}
HttpClient.PostAsJsonAsync already encodes to JSON, so skip the JsonConvert.SerializeObject and just
HttpResponseMessage response = await client.PostAsJsonAsync("api/Account/RegisterList",
registrations);

Categories