Rest API Calls with RestSharp calling ASP.NET Web API - api

I'm currently testing out writing a RESTful API with ASP.NET Web API. I'm using RestSharp on a client to simulate different calls.
I want to submit an application ID query string, and the body should be a collection of type "Log". Every time, the application ID get's posted by the body received by the server is always NULL.
Code on the server:
public class LogsController : ApiController
{
public HttpStatusCode Post(Guid ID, [FromBody] List<Log> logs)
{
if (logs != null)
return HttpStatusCode.OK;
else
return HttpStatusCode.PreconditionFailed;
}
}
public class Log
{
public Guid ErrorId { get; set; }
}
Code on the client:
static void Main(string[] args)
{
var client = new RestClient("http://localhost:36146/api");
var json = JsonConvert.SerializeObject(new List<Log>()
{
new Log { ErrorId = Guid.NewGuid()}
});
var request = new RestRequest("Logs", Method.POST);
request.RequestFormat = DataFormat.Json;
request.AddParameter("ID", Guid.NewGuid(), ParameterType.QueryString);
request.AddHeader("Accept", "application/json");
request.AddHeader("Content-Type", "application/json; charset=utf-8");
request.AddBody(json);
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);
Console.Read();
}
public class Log
{
public Guid ErrorId { get; set; }
}
I thought I got this working, however no matter what I do now the "logs" parameter on the server is always NULL.

I think I've found the issue.
RestSharp implicitly uses the JsonSerializer when populating the body of the request. As I was also called the Serializer I think it caused issues with the formatting.
I've removed that call to the serializer and now I'm receiving a 200 back from the server.
Happy days.

Related

RestSharp RestClient not working with ASP.NET Core [FromForm]

Hi I am writing integration test for my asp.net core project and i am trying to use RestSharp RestClient to send Form Data. My Code is working fine as i am getting the desired result if use postman but if i copy restsharp code from postman and try to implement in my test case its not reaching the controller. Following is my code
Postman -
Test Code -
[Test]
public void ConvertToJson_CSV()
{
var client = new RestClient("https://localhost:44355/GroupContacts/ConvertToJson");
client.Timeout = -1;
var request = new RestRequest(Method.POST);
//request.AddFile("files", #"C:\Users\RanaBoy\Desktop\ZTT\ZTTTestFiles\sample500.csv");
request.AddFile("files", System.IO.File.ReadAllBytes(#"C:\Users\RanaBoy\Desktop\ZTT\ZTTTestFiles\sample500.csv"), "sample500.csv");
request.AddParameter("optInStatus", "1");
request.AddParameter("SessionId", _SessionId);
request.AddParameter("AccountId", _AccountId);
request.AddHeader("cache-control", "no-cache");
request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
request.AddHeader("content-type", "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW");
request.AlwaysMultipartFormData = true;
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);
//Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
}
Controller Code -
[HttpPost]
[Route("ConvertToJson")]
[Consumes("multipart/form-data")]
public async Task<IActionResult> ConvertToJson([FromForm] ConvertFileToJsonCommand command)
{
var result = await _mediator.Send(command);
return Ok(result.ResultJson);
}
Model Class -
public class ConvertFileToJsonCommand : IRequest<ConvertFileToJsonResponse>
{
public int AccountId { get; set; }
public string SessionId { get; set; }
public List<IFormFile> files { get; set; }
public int optInStatus { get; set; }
}
Postman doesn't generate the correct code for RestSharp. For example, your code sets the content-type header twice.
The following code should produce the correct request:
public async Task ConvertToJson_CSV()
{
var client = new RestClient("https://localhost:44355/GroupContacts/ConvertToJson");
var request = new RestRequest(Method.POST)
.AddFile("files", "sample500.csv", "C:\Users\RanaBoy\Desktop\ZTT\ZTTTestFiles\sample500.csv")
.AddParameter("optInStatus", "1")
.AddParameter("SessionId", _sessionId)
var response = await client.ExecuteAsync(request);
Console.WriteLine(response.Content);
//Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
}
Notice that the code uses RestSharp 107. If for some reason it won't work, you can check the difference between Postman request and RestSharp request using HttpTracer (check the docs).

415 Unsupported Media Type in ASP.NET core web api

I am trying to experiment with asp.net core web api so I made some simple api with a controller like this:
[ApiController]
[Route("MyController")]
public class MyController : ControllerBase
{
[HttpGet]
[Route("GetResult")]
public IActionResult GetResult(string param1, string param2= null, SomeClassObj obj = null)
{ .... }
}
I ran the api locally and sent this postman GET request:
https://localhost:5001/MyController/GetResult?param1=someString
I got the error: 415 Unsupported Media Type
What am I missing here so it could work?
I was getting the same error after invoking the WEB API from .NET MVC.
As suggested by #zhulien, I have changed from [FromBody] to [FromForm] in WebAPI, it works fine for me.
.NET Core WebAPI method.
public async Task<IActionResult> Login([FromForm] LoginModel loginInfo)
{ // JWT code here }
.Net Core MVC Action Method.
public async void InvokeLoginAPIAsync(string endPoint, string userName, string pwd)
{
configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
baseUrl = configuration["Application:BaseAPI"] ?? throw new Exception("Unable to get the configuration with key Application:BaseAPI");
string targetUrl = string.Format("{0}/{1}", baseUrl, endPoint);
using (HttpClient deviceClient = new HttpClient())
{
var request = new HttpRequestMessage(HttpMethod.Post, targetUrl);
var data = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("userName", userName),
new KeyValuePair<string, string>("password", pwd)
};
request.Content = new FormUrlEncodedContent(data);
using (var response = await deviceClient.SendAsync(request))
{
if (response.StatusCode == HttpStatusCode.OK)
{
TempData["Response"] = JsonConvert.SerializeObject(response.Content);
}
}
}
}
Which version of .NET Core are you using?
Try doing the request from the browser and see if you have the same result.
Also, are you sure you're doing a GET and not a POST request in Postman? You shouldn't get 415 errors for GET requests, especially when you're not sending any body.
This error mainly occurs when you try to send a body and you haven't specified the media-type through the Content-Type header.
Ensure that the request is GET and your body is empty.
Solution after post edit:
As you're trying to parse a DTO object(SomeClassObj), you should specify where the values should come from. In order to fix your specific case, add the [FromQuery] attribute before SomeClassObj.
Your code should look like this:
[ApiController]
[Route("MyController")]
public class MyController : ControllerBase
{
[HttpGet]
[Route("GetResult")]
public IActionResult GetResult(string param1, string param2= null, [FromQuery]SomeClassObj obj = null)
{ .... }
}
This tells the parser to fetch the data from the query string. This will fix the 415 issue. However, if you want to bind to complex types, especially on get, checkout those topics: ASP.NET CORE 3.1 Model Binding and this issue as you will most probably encounter issues with parsing your DTO object.
Use [FromForm] attribute before each argument in the controller function.

Post value always null between Python request and ASP.Net Core API

I have some simple Python
import requests
response = requests.post(url+'users/', data = 'hallo')
And a simple ASP.Net Core API
public void Post(string value)
{
var newUser = JsonConvert.DeserializeObject<User>(value);
// [...]
}
When I breakpoint the C# code the incoming parameter 'value' is always null. I have tried sending JSON instead of a simple string, setting the headers to 'text/plain', 'application/json', or 'application/x-www-form-urlencoded' in Python but the result is the same. I have tried decorating the parameter [FromBody] in the ASP.Net Core API, but then I get a 400 error ("The input was invalid").
What am I missing?
(EDIT. Here is a hacky fix, definitely not an answer, but it may help people see what's wrong.
public void Post(string value)
{
Request.EnableRewind();
var body = "";
using (var reader = new StreamReader(Request.Body))
{
Request.Body.Seek(0, SeekOrigin.Begin);
body = reader.ReadToEnd();
}
value = body;
var newUser = JsonConvert.DeserializeObject<User>(value);
// [...]
}
Then value is set correctly. But if the value is sitting there in the body of the post, it is frustrating that using [FromBody] results in a 400 error.)
Hi dumbledad I had this same issue, I guess you would have solved it but I wanted to post the solution here.
I had to send the data to params instead of data.
So on the Python side:
payload = {"type": "2"}
myheaders={'Content-type':'application/json', 'Accept':'application/json'}
r = requests.post(url=myurl,headers=myheaders, params=payload)
On the c# side:
[Route("MyFunction")]
[HttpPost]
public ActionResult GetOpenFootball_(string type){}
This mapped the value "2" to the string parameter called "type".
EDIT
I managed to get objects through the [From Body] tag in this way:
I changed the header to:
_headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
I put my parameters into a json such as:
jsondata = {"username":"selzero"}
Then I plugged that into the json property in request like:
r = requests.post(url=_url, headers=_headers,json=jsondata)
This works, MVC controller [FromBody] likes it.
If you make your request like this:
response = requests.post(url, data={'value' : 'hallo'})
Then this will associate 'hallo' with the method-parameter value:
[HttpPost]
public void Post(string value)
{
Console.WriteLine(value);
}
If you need to send more data, you could also wrap your request in an object and fetch it by model-binding. See more here: Model Validation in ASP.NET Web API. It looks like that's what you're attempting to do anyways here:
var newUser = JsonConvert.DeserializeObject<User>(value);
You can achieve this automatically like this:
response = requests.post(url, json={ 'FirstName' : 'john', 'LastName' : 'doe' })
Notice the use of json=. And in your controller:
public class User {
public string FirstName { get; set; }
public string LastName { get; set; }
}
[HttpPost]
public void Post([FromBody] User user)
{
Console.WriteLine(user.FirstName);
Console.WriteLine(user.LastName);
}
// Outputs:
// john
// doe

Web API 2 Post 404s, but Get works

I'm confused... I have a very simple Web API and controller, which works fine if I have a GET request, but 404's if I have a POST request.
[RoutePrefix("api/telemetry/trial")]
public class LoginTelemetryController : ApiController
{
[Route("login")]
[HttpPost]
public IHttpActionResult RecordLogin(string appKey) {
using (var context = new Core.Data.CoreContext()) {
context.ActivityLogItems.Add(new Domain.Logging.ActivityLogItem()
{
ActivityType = "Trial.Login",
DateUtc = DateTime.UtcNow,
Key = new Guid(appKey)
});
context.SaveChanges();
}
return Ok();
}
When I post against this in postman, I get:
{
"message": "No HTTP resource was found that matches the request URI 'http://localhost:47275/api/telemetry/trial/login'.",
"messageDetail": "No action was found on the controller 'LoginTelemetry' that matches the request."
}
If I change it to a [HttpGet] and put the appKey as a querystring, all is fine.
My app startup is very simple:
public void Configuration(IAppBuilder app)
{
log4net.Config.XmlConfigurator.Configure();
HttpConfiguration httpConfig = new HttpConfiguration();
httpConfig.MapHttpAttributeRoutes(); // <------ HERE
FilterConfig.RegisterHttpFilters(httpConfig.Filters);
LoggingConfig.RegisterHandlers(httpConfig.Services);
ConfigureOAuth(app);
ConfigureWebApi(httpConfig);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(httpConfig);
}
Can anyone spot why POST requests aren't being found? Thanks
If I take string parameter out and replace it with a request object, it works...
Instead of: public IHttpActionResult RecordLogin(string appKey)
I create a request model class:
public class PostLoginTelemetryRequest{
public string appKey {get;set;}
}
Then alter the signature:
public IHttpActionResult RecordLogin(PostLoginTelemetryRequest request)
Everything works fine (why it can't take a regular string like MVC5 web dev, I don't know, but anyway...)
(also note that I had tried this in every format from the client with the string method: form-url-encode, raw body, etc, so I'm fairly certain it wasn't a calling format issue).

How to create an ASP.NET Core API controller action for HttpPost that can accept a request that has content of type MultipartFormDataContent

I am working on an ASP.NET Core 2.0 API in VS2017.
I want to create a controller action for an HTTP Post method that accept string and byte[] values that I will then use to create records in my SQL database.
From what I understand, if I want to post both string data and a byte[] that represents a file, I have to use MultipartFormDataContent as the type of content in the request from my client.
So, on the API controller action, how is that mapped? Can I have a DTO class in the API that has properties for both the string values and the byte[] value and have it passed into the API controller action via the [FromBody]UploadsDto dto
For example, have a DTO class like this...
public class UploadFileRecordForCreationDto
{
public int LocationId { get; set; }
public string FileName { get; set; }
public byte[] UploadedFile { get; set; }
}
Then have a controller action with this signature...
[HttpPost(Name = "CreateUploadFileRecord")]
public IActionResult CreateUploadFileRecord([FromBody]UploadFileRecordForCreationDto dto)
{
...
...
...
return CreatedAtRoute("GetUploadedFileFile", new { id = linkedResourceToReturn["Id"] }, linkedResourceToReturn);
}
And then have that API action accept a request created using something similar to what I am doing with this test console application on the client side;
static async Task CreateUploadFileRecordAsync()
{
httpClient.BaseAddress = new Uri("https://localhost:44369");
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
string relativeUrl = "/api/UploadFilesManager";
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, relativeUrl);
HttpResponseMessage response = new HttpResponseMessage();
using (var content = new MultipartFormDataContent("--UploadTest"))
{
var values = new[]
{
new KeyValuePair<string,string>("LocationId","1"),
new KeyValuePair<string,string>("FileName","TestFile-01.txt"),
};
foreach (var keyvaluepair in values)
{
content.Add(new StringContent(keyvaluepair.Value, Encoding.UTF8, "application/json"), keyvaluepair.Key);
}
var fileContent = new ByteArrayContent(File.ReadAllBytes(#"C:\testfile-01.txt"));
fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue(DispositionTypeNames.Attachment)
{
Name = "UploadedFile",
FileName = "testfile-01.txt"
};
content.Add(fileContent);
request.Content = content;
response = await httpClient.SendAsync(request);
}
if (response.IsSuccessStatusCode)
{
string result = response.Headers.Location.ToString();
Console.WriteLine("Success:\n");
Console.WriteLine($"New Record Link: [{result}]\n");
}
else
{
Console.WriteLine($"Failed to create new UploadFile record. Error: {0}\n", response.ReasonPhrase);
}
}
If it doesn't just map to a DTO in the FromBody, can anyone provide an example of how to deal with this use case?