Post value always null between Python request and ASP.Net Core API - asp.net-core

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

Related

CustomActionFilter not getting called for POST/PUT endpoint in web api [duplicate]

I am using .NET Core 2.2 with Web API. I have created one class, i.e., as below:
public class NotificationRequestModel
{
[Required]
public string DeviceId { get; set; }
[Required]
public string FirebaseToken { get; set; }
[Required]
public string OS { get; set; }
public int StoreId { get; set; }
}
Using the above class I have created one method. Now I want to return a custom object, but it's returning its own object.
API method is:
public ActionResult<bool> UpdateFirebaseToken(NotificationRequestModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(FormatOutput(ModelState.Values));
}
var result = _notificationService.InsertOrUpdateFirebaseToken(model);
return Ok(result);
}
Here FormatOutput method is format the output.
protected Base FormatOutput(object input, int code = 0, string message = "", string[] details = null)
{
Base baseResult = new Base();
baseResult.Status = code;
baseResult.Error = message;
baseResult.TimeStamp = CommonHelper.CurrentTimeStamp;
baseResult.Code = code;
baseResult.Details = details;
baseResult.Message = message; //Enum.Parse<APIResponseMessageEnum>(code.ToString(), true); // (enum of code get value from language)
return baseResult;
}
But the issue is it returns:
{
"errors": {
"DeviceId": [
"The DeviceId field is required."
]
},
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "80000049-0001-fc00-b63f-84710c7967bb"
}
I want to customize this error with my model. I need error message and details from return output and passed it to my model. How can I do that? I had try to debug my code and found that breakpoint on API method is not calling. So I can't handle my custom method. Is there any solution? What am I doing wrong?
When using a controller with the ApiController attribute applied, ASP.NET Core automatically handles model validation errors by returning a 400 Bad Request with ModelState as the response body. As such, your conditional testing ModelState.IsValid is essentially always false (and therefore not entered) because the only requests that will ever get this far are valid ones.
You could simply remove the ApiController attribute, but that removes a bunch of other beneficial stuff the attributes adds as well. The better option is to use a custom response factory:
services.Configure<ApiBehaviorOptions>(o =>
{
o.InvalidModelStateResponseFactory = actionContext =>
new BadRequestObjectResult(actionContext.ModelState);
});
That's essentially what's happening by default, so you'd simply need to change the action provided there accordingly to customize it to your whims.
As Chris analyzed, your issue is caused by Automatic HTTP 400
responses.
For the quick solution, you could suppress this feature by
services.AddMvc()
.ConfigureApiBehaviorOptions(options => {
options.SuppressModelStateInvalidFilter = true;
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
For an efficient way, you could follow the suggestion from Chris, like below:
services.AddMvc()
.ConfigureApiBehaviorOptions(options => {
//options.SuppressModelStateInvalidFilter = true;
options.InvalidModelStateResponseFactory = actionContext =>
{
var modelState = actionContext.ModelState.Values;
return new BadRequestObjectResult(FormatOutput(modelState));
};
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
And, there isn't any need to define the code below any more in your action.
if (!ModelState.IsValid)
{
return BadRequest(FormatOutput(ModelState.Values));
}

How can I customize the error response in Web API with .NET Core?

I am using .NET Core 2.2 with Web API. I have created one class, i.e., as below:
public class NotificationRequestModel
{
[Required]
public string DeviceId { get; set; }
[Required]
public string FirebaseToken { get; set; }
[Required]
public string OS { get; set; }
public int StoreId { get; set; }
}
Using the above class I have created one method. Now I want to return a custom object, but it's returning its own object.
API method is:
public ActionResult<bool> UpdateFirebaseToken(NotificationRequestModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(FormatOutput(ModelState.Values));
}
var result = _notificationService.InsertOrUpdateFirebaseToken(model);
return Ok(result);
}
Here FormatOutput method is format the output.
protected Base FormatOutput(object input, int code = 0, string message = "", string[] details = null)
{
Base baseResult = new Base();
baseResult.Status = code;
baseResult.Error = message;
baseResult.TimeStamp = CommonHelper.CurrentTimeStamp;
baseResult.Code = code;
baseResult.Details = details;
baseResult.Message = message; //Enum.Parse<APIResponseMessageEnum>(code.ToString(), true); // (enum of code get value from language)
return baseResult;
}
But the issue is it returns:
{
"errors": {
"DeviceId": [
"The DeviceId field is required."
]
},
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "80000049-0001-fc00-b63f-84710c7967bb"
}
I want to customize this error with my model. I need error message and details from return output and passed it to my model. How can I do that? I had try to debug my code and found that breakpoint on API method is not calling. So I can't handle my custom method. Is there any solution? What am I doing wrong?
When using a controller with the ApiController attribute applied, ASP.NET Core automatically handles model validation errors by returning a 400 Bad Request with ModelState as the response body. As such, your conditional testing ModelState.IsValid is essentially always false (and therefore not entered) because the only requests that will ever get this far are valid ones.
You could simply remove the ApiController attribute, but that removes a bunch of other beneficial stuff the attributes adds as well. The better option is to use a custom response factory:
services.Configure<ApiBehaviorOptions>(o =>
{
o.InvalidModelStateResponseFactory = actionContext =>
new BadRequestObjectResult(actionContext.ModelState);
});
That's essentially what's happening by default, so you'd simply need to change the action provided there accordingly to customize it to your whims.
As Chris analyzed, your issue is caused by Automatic HTTP 400
responses.
For the quick solution, you could suppress this feature by
services.AddMvc()
.ConfigureApiBehaviorOptions(options => {
options.SuppressModelStateInvalidFilter = true;
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
For an efficient way, you could follow the suggestion from Chris, like below:
services.AddMvc()
.ConfigureApiBehaviorOptions(options => {
//options.SuppressModelStateInvalidFilter = true;
options.InvalidModelStateResponseFactory = actionContext =>
{
var modelState = actionContext.ModelState.Values;
return new BadRequestObjectResult(FormatOutput(modelState));
};
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
And, there isn't any need to define the code below any more in your action.
if (!ModelState.IsValid)
{
return BadRequest(FormatOutput(ModelState.Values));
}

Model Binding for multipart/form-data (File + JSON) post in ASP.NET Core 1.1

I'm attempting to build an ASP.NET Core 1.1 Controller method to handle an HTTP Request that looks like the following:
POST https://localhost/api/data/upload HTTP/1.1
Content-Type: multipart/form-data; boundary=--------------------------625450203542273177701444
Host: localhost
Content-Length: 474
----------------------------625450203542273177701444
Content-Disposition: form-data; name="file"; filename="myfile.txt"
Content-Type: text/plain
<< Contents of my file >>
----------------------------625450203542273177701444
Content-Disposition: form-data; name="text"
Content-Type: application/json
{"md5":"595f44fec1e92a71d3e9e77456ba80d0","sessionIds":["123","abc"]}
----------------------------625450203542273177701444--
It's a multipart/form-data request with one part being a (small) file and the other part a json blob that is based on a provided specification.
Ideally, I'd love my controller method to look like:
[HttpPost]
public async Task Post(UploadPayload payload)
{
// TODO
}
public class UploadPayload
{
public IFormFile File { get; set; }
[Required]
[StringLength(32)]
public string Md5 { get; set; }
public List<string> SessionIds { get; set; }
}
But alas, that doesn't Just Work {TM}. When I have it like this, the IFormFile does get populated, but the json string doesn't get deserialized to the other properties.
I've also tried adding a Text property to UploadPayload that has all the properties other than the IFormFile and that also doesn't receive the data. E.g.
public class UploadPayload
{
public IFormFile File { get; set; }
public UploadPayloadMetadata Text { get; set; }
}
public class UploadPayloadMetadata
{
[Required]
[StringLength(32)]
public string Md5 { get; set; }
public List<string> SessionIds { get; set; }
}
A workaround that I have is to avoid model binding and use MultipartReader along the lines of:
[HttpPost]
public async Task Post()
{
...
var reader = new MultipartReader(Request.GetMultipartBoundary(), HttpContext.Request.Body);
var section = await reader.ReadNextSectionAsync();
var filePart = section.AsFileSection();
// Do stuff & things with the file
section = await reader.ReadNextSectionAsync();
var jsonPart = section.AsFormDataSection();
var jsonString = await jsonPart.GetValueAsync();
// Use $JsonLibrary to manually deserailize into the model
// Do stuff & things with the metadata
...
}
Doing the above bypasses model validation features, etc. Also, I thought maybe I could take that jsonString and then somehow get it into a state that I could then call await TryUpdateModelAsync(payloadModel, ...) but couldn't figure out how to get there either - and that didn't seem all that clean either.
Is it possible to get to my desired state of "transparent" model binding like my first attempt? If so, how would one get to that?
The first problem here is that the data needs to be sent from the client in a slightly different format. Each property in your UploadPayload class needs to be sent in its own form part:
const formData = new FormData();
formData.append(`file`, file);
formData.append('md5', JSON.stringify(md5));
formData.append('sessionIds', JSON.stringify(sessionIds));
Once you do this, you can add the [FromForm] attribute to the MD5 property to bind it, since it is a simple string value. This will not work for the SessionIds property though since it is a complex object.
Binding complex JSON from the form data can be accomplished using a custom model binder:
public class FormDataJsonBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if(bindingContext == null) throw new ArgumentNullException(nameof(bindingContext));
// Fetch the value of the argument by name and set it to the model state
string fieldName = bindingContext.FieldName;
var valueProviderResult = bindingContext.ValueProvider.GetValue(fieldName);
if(valueProviderResult == ValueProviderResult.None) return Task.CompletedTask;
else bindingContext.ModelState.SetModelValue(fieldName, valueProviderResult);
// Do nothing if the value is null or empty
string value = valueProviderResult.FirstValue;
if(string.IsNullOrEmpty(value)) return Task.CompletedTask;
try
{
// Deserialize the provided value and set the binding result
object result = JsonConvert.DeserializeObject(value, bindingContext.ModelType);
bindingContext.Result = ModelBindingResult.Success(result);
}
catch(JsonException)
{
bindingContext.Result = ModelBindingResult.Failed();
}
return Task.CompletedTask;
}
}
You can then use the ModelBinder attribute in your DTO class to indicate that this binder should be used to bind the MyJson property:
public class UploadPayload
{
public IFormFile File { get; set; }
[Required]
[StringLength(32)]
[FromForm]
public string Md5 { get; set; }
[ModelBinder(BinderType = typeof(FormDataJsonBinder))]
public List<string> SessionIds { get; set; }
}
You can read more about custom model binding in the ASP.NET Core documentation: https://learn.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-model-binding
I'm not 100% clear on how this would work for ASP.NET Core but for Web API (so I assume a similar path exists here) you'd want to go down the road of a Media Formatter. Here's an example (fairly similar to your question) Github Sample with blog post
Custom formatters might be the ticket? https://learn.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-formatters

Getting hold of raw POST data when using [FromBody]

I have a controller running on ASP.NET Core 1.0 RC2 and I'd like to dump the raw POST data out to telemetry as ApplicationInsights doesn't do this for you. My code looks like this
[HttpPost]
[Produces("application/json")]
public IActionResult Post([FromBody] RequestClass RequestData)
{
var stream = this.HttpContext.Request.Body;
stream.Position = 0;
using (var reader = new StreamReader(stream))
{
string body = reader.ReadToEnd();
Telemetry.TrackTrace(body, Microsoft.ApplicationInsights.DataContracts.SeverityLevel.Information);
}
return Ok();
}
But the string "body" always comes up empty. If I remove the [FromBody] decoration from the function signature, then this code works, but the RequestData object only contains null, which isn't what I want.
The only thing I can think of is converting RequestData back to a Json string, but this feels clunky and slow.
(EDIT: The POST data is Json)
You need to enable buffering the request body:
services.Configure<FormOptions>(options => options.BufferBody = true);
https://github.com/aspnet/HttpAbstractions/blob/dev/src/Microsoft.AspNetCore.Http/Features/FormOptions.cs#L20
The most simple way I found to solve this issue is to use jObject as the Model.
And send the request with Content-Type: application/json at the header.
Use something like NewtonSoft's json dll this:
[HttpPost]
public IActionResult Post([FromBody] jObject RequestData)
{
string str = RequestData["key"];
return Ok();
}
This worked for me
The request stream has already been read, so what you can do here is EnableRewind on the Request
See solution here for reading json from body. Same should apply to your issue.

Rest API Calls with RestSharp calling ASP.NET Web 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.