I have an MVC application with two micro services. In the first I call PostAsync() method to send my custom object
public class Logo {
public Guid Id { get; set; }
public IEnumerable<byte> Content { get; set; }
}
to another service
public async Task PostLogo(Logo logo)
{
using (var client = new HttpClient())
{
await client.PostAsync(_url, new StringContent(JsonConvert.SerializeObject(logo), Encoding.UTF8, "application/json"));
}
}
In the second micro service I try to deserialize using
[HttpPost, Route("logo")]
public Task<FileUploadResultModel> SaveAsync([FromBody]Logo model)
{
return _fileService.SaveAsync(null);
}
but it gets null instead of input object
.
Can anyone explain how can I send/process custom object using Post request, please?
Update action to follow preferred syntax structure.
[HttpPost, Route("logo")]
public async Task<IHttpActionResult> SaveAsync([FromBody]Logo model) {
if(ModelState.IsValid) {
FileUploadResultModel result = await _fileService.SaveAsync(model);
return Ok(result);
}
return BadRequest();
}
Based on comments it is possible that the byte array is too large so the model binder is unable to properly bind the model. Check the web.config and see what is the max size of data that can be sent. You may need to increase it. The max is 2GB but I would advise against such a high value.
Check this answer: Maximum request length exceeded
If you are able to get the image smaller you should base64 Url encode that image to a string and sent that as the content.
public class Logo {
public Guid Id { get; set; }
public string Content { get; set; }
}
And when you get the model convert the content back to a byte array
[HttpPost, Route("logo")]
public async Task<IHttpActionResult> SaveAsync([FromBody]Logo model) {
if(ModelState.IsValid) {
byte[] content = Convert.FromBase64String(model.Content);
var id = model.Id;
//...
return Ok(result);
}
return BadRequest();
}
Related
I am trying to make a post request from WPF to Web API using the following code but the request parameter is always null.
Request Model
public class Document
{
public string FileName { get; set; }
public byte[] Buffer { get; set; }
}
public class Request
{
public string Uploader { get; set; }
public List<Document> Documents { get; set; }
}
WPF Client
var obj = new Request()
{
Uploader = "John Doe",
Documents = new List<Document>
{
new Document()
{
FileName ="I Love Coding.pdf",
Buffer = System.IO.File.ReadAllBytes(#"C:\Users\john.doe\Downloads\I Love Coding.pdf.pdf")
}
}
};
using (var http = new HttpClient())
{
var encodedJson = JsonConvert.SerializeObject(obj);
var conent = new StringContent(encodedJson, Encoding.UTF8, "application/json");
HttpResponseMessage response = await http.PostAsync("https://my-app.com/api/upload", conent);
response.EnsureSuccessStatusCode();
}
Web API
[Route("")]
public class AppController : ControllerBase
{
[HttpPost]
[Route("api/upload")]
public async Task<IActionResult> UploadDocumentsAsync([FromBody] Request request)
{
// request is always null when app is running in production
// https://my-app.com/api/upload
//request is not null when running on https://localhost:8080/api/upload
}
}
Please what am I missing in the above implementation?
The request parameter is not null on localhost but always null in production.
Please what am I missing in the above implementation? The request
parameter is not null on localhost but always null in production.
Well, not sure how are getting data on local server becuse, you are sending MultipartFormData means your POCO object and file buffer. As you may know we can send json object in FromBody but not the files as json. Thus, I am not sure how it working in local and getting null data is logical in IIS Or Azure.
what am I missing in the above implementation?
As explained above, for sending both POCO object and Files as byte or steam we need to use FromForm and beside that, we need to bind our request object as MultipartFormDataContent to resolve your null data on your UploadDocumentsAsync API action.
Required Change For Solution:
WPF:
In your WPF http request please update your request code snippet as following:
var obj = new Request()
{
Uploader = "John Doe",
Documents = new List<Document>
{
new Document()
{
FileName ="I Love Coding.pdf",
Buffer = System.IO.File.ReadAllBytes(#"YourFilePath")
}
}
};
var httpClient = new HttpClient
{
BaseAddress = new("https://YourServerURL")
};
var formContent = new MultipartFormDataContent();
formContent.Add(new StringContent(obj.Uploader), "Uploader");
formContent.Add(new StringContent(obj.Documents[0].FileName), "Documents[0].FileName");
formContent.Add(new StreamContent(new MemoryStream(obj.Documents[0].Buffer)), "Documents[0].Buffer", obj.Documents[0].FileName);
var response = await httpClient.PostAsync("/api/upload", formContent);
if (response.IsSuccessStatusCode)
{
var responseFromAzureIIS = await response.Content.ReadAsStringAsync();
}
Note: Class in WPF side would remain same as before. No changes required.
Asp.net Core Web API:
In asp.net core web API side you should use [FromForm] instead of [FromBody]
So your controller Action would as following:
[Route("")]
public class AppController : ControllerBase
{
[HttpPost]
[Route("api/upload")]
public async Task<IActionResult> UploadDocumentsAsync([FromForm] Request file)
{
if (file.Documents[0].Buffer == null)
{
return Ok("Null File");
}
return Ok("File Received");
}
}
Note: For remote debugging I have checked the logs and for double check I have used a simple conditionals whether file.Documents[0].Buffer == null. I have tested both in local, IIS and Azure and working accordingly.
Update POCO Class in API Project:
For buffer you have used byte for your WPF project but for Web API project update that to IFormFile instead of byte. It should be as following:
public class Document
{
public string FileName { get; set; }
public IFormFile Buffer { get; set; }
}
public class Request
{
public string Uploader { get; set; }
public List<Document> Documents { get; set; }
}
Output:
If you would like to know more details on it you could check our official document here
I used the HttpResponseMessage Post method to let the mobile terminal verify the account password. I used the following CODE to run successfully, but the POST format must be run like this.
'{"ID":"xxx","Password":"xxx"}'
It need two ' can run, I don't know why.
I can't request a service using the normal POST format on iOS or Android.
The format I want is {"ID":"xxx","Password":"xxx"},without '
[HttpPost]
public HttpResponseMessage Post([FromBody] string DATA)
{
using (appapidataEntities entities = new appapidataEntities())
{
//string controllerName = ControllerContext.RouteData.Values["controller"].ToString();
JObject jo = JObject.Parse(DATA);
string id = jo["ID"].ToString();
string password = jo["Password"].ToString();
var user = entities.USERs.Where(x => x.ID == id && x.Password == password).FirstOrDefault();
var result = new
{
message = "failure"
};
var result2 = new
{
message = "success"
};
if (user == null)
{
return Request.CreateResponse(HttpStatusCode.OK, result);
}
else
{
return Request.CreateResponse(HttpStatusCode.OK, result2);
}
}
}
public partial class USER
{
public string ID { get; set; }
public string Password { get; set; }
}
}
Please have someone with experience to help me, thank you very much.
As #Nkosi said, the correct way to receive a complex object is using a class/object (also called 'model binding')
In your models add this class. This class will be the 'contract' between the service with any external application who calls the service. Usually, any client (service, app or frontend) also has a class with this contract to call the service.
public class LoginViewModel {
public string ID { get; set; }
public string Password { get; set; }
}
Now, modify the controller as follow
[HttpPost]
public HttpResponseMessage Post([FromBody] LoginViewModel DATA) {
using (appapidataEntities entities = new appapidataEntities())
string id = DATA.ID;
string password = DATA.Password;
// rest of the code
}
}
Make sure the device is sending the data the service is waiting (maybe adding a breakpoint if you are debugging from Android Studio before to make the request) and add a breakpoint in your controller to verify that the variable DATA has the correct values.
I have a asp.net core (v2.1) webapi project which expose this function:
[HttpPost]
[Route("v1/do-something")]
public async Task<IActionResult> PostDoSomething(ModelData model)
{
//...
}
and this model:
public class ModelData
{
[Required]
public string Email { get; set; }
}
I want to make this endpoint flexible, in content type perspective. Therefore, it should be OK to send this endpoint diffrent content type in body.
For example, those "BODY" argument will be allowed:
// application/x-www-form-urlencoded
email="abc123#gmail.com"
// application/json
{
"email": "abc123#gmail.com"
}
In contrast to the old .net framework, in dotnet core this is not allowed out of the box. I found that I need to add Consume attribute with [FormForm] attribute. But, If I add the [FormForm] attribute the the model argument, it wont work anymore on JSON (for example) - because then it should be [FromBody].
I though that it will be ok to use code like this:
[HttpPost]
[Route("v1/do-something")]
public async Task<IActionResult> PostDoSomething([FromBody] [FromForm] ModelData model)
{
//...
}
But as you can expect, this code not working.
So, in order to achieve this flexibility I have to duplicate all my endpoint - which sound like a very very bad idea.
[HttpPost]
[Route("v1/do-something")]
[Consume ("application/json")]
public async Task<IActionResult> PostDoSomething([FromBody] ModelData model)
{
//...
}
[HttpPost]
[Route("v1/do-something")]
[Consume ("application/x-www-form-urlencoded")]
public async Task<IActionResult> PostDoSomething([FromForm] ModelData model)
{
//...
}
// ... Other content types here ...
It's sound an easy task. But seems like more complicated.
I missed something? How to make an endpoint work in any content type?
Here is a custom model binder that binds based on the content type.
public class BodyOrForm : IModelBinder
{
private readonly IModelBinderFactory factory;
public BodyOrForm(IModelBinderFactory factory) => this.factory = factory;
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var contentType =
bindingContext.ActionContext.HttpContext.Request.ContentType;
BindingInfo bindingInfo = new BindingInfo();
if (contentType == "application/json")
{
bindingInfo.BindingSource = BindingSource.Body;
}
else if (contentType == "application/x-www-form-urlencoded")
{
bindingInfo.BindingSource = BindingSource.Form;
}
else
{
bindingContext.Result = ModelBindingResult.Failed();
}
var binder = factory.CreateBinder(new ModelBinderFactoryContext
{
Metadata = bindingContext.ModelMetadata,
BindingInfo = bindingInfo,
});
await binder.BindModelAsync(bindingContext);
}
}
Tested with the following action.
[HttpPost]
[Route("api/body-or-form")]
public IActionResult PostDoSomething([ModelBinder(typeof(BodyOrForm))] ModelData model)
{
return new OkObjectResult(model);
}
Here is a demo is on GitHub.
I am using .net core C#, WebApi & AngularJs.
For saving data my Angularjs code makes a $http call to my WebApi. I can return single data from my api fine but not sure whats the best method to return multiple values here. I can make it comma separated and then return as well, but wanted to know if there is a better approach to this.
So basically when the API saves data to my db, I want to return a variable, boolean value if the save was successful and an exception message in case the save was not successfully. Below is my code.
AngularJs Code:
service.saveData(data).then(function (res) {
//get someDataToReturn, dataSaved & exception raised if any from db save here.
}, function (err) {
});
WebApi Code:
[HttpPost("data/save")]
public async Task<IActionResult> SaveData([FromBody] List<UserData> data)
{
bool dataSaved = true;
string someDataToReturn = string.Empty;
//do some processing and updating someDataToReturn here
//Saving data to DB
dataSaved = SaveData(data);
//I want to return someDataToReturn, dataSaved(true or false) and exception raised from SaveData if any
return Ok(someDataToReturn);
}
//DB Call to save data
public bool SaveData(List<UserData> data)
{
try
{
foreach (var set in data)
{
//creating query etc
_db.Execute(query);
}
return true;
}
catch (SqlException ex)
{
}
return false;
}
Let me know the best approach for this.
First of, you should check if the values in your request body is correctly populated.
Take a look at DataAnnotations.
You can use annotations to specify which properties in your model that are Required, Min and Maxlength etc.
Here's an example on how to define a Name property to be required on the UserData class
public class UserData
{
[Required]
public string Name { get; set; }
}
If the request model do not fulfill the specified rules set on the UserData class DataAnnotations, the context ModelState will be set to false and contain the DataAnnotations errors.
This can be used to determind if the current request is a bad request and return a proper http status code from that.
[HttpPost("data/save")]
public async Task<IActionResult> SaveData([FromBody] List<UserData> data)
{
if (!ModelState.IsValid)
return BadRequest(ModelState); //will return a 400 code
...
Then regarding the SaveData method. Capture the exception in the controller and return a proper status code from there
[HttpPost("data/save")]
public async Task<IActionResult> SaveData([FromBody] List<UserData> data)
{
if (!ModelState.IsValid)
return BadRequest(ModelState); //400 status code
try
{
SaveData(data);
}
catch(Exception e)
{
return InternalServerError(e); //500 status code
}
string someDataToReturn = string.Empty;
return Ok(someDataToReturn ); //200 status code
}
public void SaveData(List<UserData> data)
{
foreach (var set in data)
{
//creating query etc
_db.Execute(query);
}
}
You can use the Controller class method Json(object data). Something like:
[HttpPost("data/save")]
public async Task<IActionResult> SaveData([FromBody] List<UserData> data)
{
return this.Json(SaveData(data));
}
See this.
you can create an entity and return it
public class BaseResult{
public bool Result{get;set;}
public string Errors{get;set;}
}
or only
return Ok( new { result = dataSaved , error= exception.Message});
the standard way is:
return 201 status code
https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201
[HttpPost("data/save")]
public async Task<IHttpActionResult> SaveData([FromBody] List<UserData> data)
{
try
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
// return response of 201 if you created the resource successfully
// typically return this with a uri to the new resource
return Created("location", saveData(data));
}
catch (Exception)
{
return InternalServerError();
}
}
I am trying to receive the below key value pair as the input parameter to my Web API
json=%7B%0A%22MouseSampleBarcode%22%20%3A%20%22MOS81%22%0A%7D%0A
where the right of the string is the URL encoded JSON which looks like
{
"MouseSampleBarcode" : "MOS81"
}
How can I parse this and store them in to the Model class
[HttpPost]
public async Task<IHttpActionResult> Get([FromBody] CoreBarCodeDTO.RootObject coreBarCode)
{
string Bar_Code = coreBarCode.MouseSampleBarcode.ToString();
where the CoreBarCodeDTO looks like below
public class CoreBarCodeDTO
{
public class RootObject
{
public string MouseSampleBarcode { get; set; }
}
}
You could do it this way. Change your class to this definition. In your controller coreBarCode.json will have the the json which you can then work with as needed:
public class CoreBarCodeDTO
{
private string _json;
public string json { get { return _json; }
set {
string decoded = HttpUtility.UrlDecode(value);
_json = decoded;
}
}
}
Update
[HttpPost]
public async Task<IHttpActionResult> Get([FromBody] CoreBarCodeDTOcoreBarCode coreBarCode)
{
string Bar_Code = coreBarCode.json;
//work with the JSON here, with Newtonsoft for example
var obj = JObject.Parse(Bar_Code);
// obj["MouseSampleBarcode"] now = "MOS81"
}
As #Lokki mentioned in his comment. The GET verb does not have a body, you need to change that to POST or PUT (depending if you are creating/searching or updating), so your code would look like this:
[HttpPost("/")]
public async Task<IHttpActionResult> Get([FromBody] CoreBarCodeDTO.RootObject coreBarCode)
{
string Bar_Code = coreBarCode.MouseSampleBarcode.ToString();
So, as I said: Get doesn't have body.
Follow #KinSlayerUY answer.
[HttpPost("/")]
public async Task<IHttpActionResult> Post([FromBody] CoreBarCodeDTO.RootObject coreBarCode)
{
string Bar_Code = coreBarCode.MouseSampleBarcode.ToString();
...
}
If you need use GET remove [FromBody] attribute and send data as single parameters
[HttpGet("/")]
public async Task<IHttpActionResult> Get(string mouseSampleBarcode)
{
var rootObject = new CoreBarCodeDTO.RootObject
{
MouseSampleBarcode = mouseSampleBarcode
}
...
}