I'm posting a file and a json object to my API using angularjs. I'm able to get all the data from the server (file and model object):
public async Task<IHttpActionResult> send()
{
var path = HttpContext.Current.Server.MapPath("~/uploads");
var provider = new MultipartFormDataStreamProvider(root);
var result = await Request.Content.ReadAsMultipartAsync(provider);
var modelFromClient = result.FormData["model"];
}
The modelFromClient i receive has this format : "model":"{\"name\":\"James\",\"comments\":\"test\"}
But i want to cast the modelFromClient json to my user model which is :
public User {
public string name{ get; set; }
public string comments{ get; set; }
}
So at the end, i want to be able to get my attributes like i used to do when i send the object explicitly trought the url :
public async Task<IHttpActionResult> add(User user) {
// here, my user object will have all the values that i set in my client side and i get them like this :
user.name;
user.comments;
...// other attributes
// Save the object in dataBase
user.Save();
}
I hope you understand my need
If you are in control of the client json, then perhaps you could omit the model field and just send the content like so:
{
"name": "james",
"comments": "test"
}
This would allow you to use your existing controller implementation "as-is".
If however, the client json is out of your control, then you need to deserialize it into a model that is representative of the json. Changing your model to the following would likely work:
public Model
{
public User model { get; set; }
}
Because you are reading out the raw json from the request, you can deserialize it manually before calling add() directly
And then your controller would change to this (Assumes Json.Net is referenced):
public async Task<IHttpActionResult> send()
{
var path = HttpContext.Current.Server.MapPath("~/uploads");
var provider = new MultipartFormDataStreamProvider(root);
var result = await Request.Content.ReadAsMultipartAsync(provider);
var modelFromClient = result.FormData["model"];
var clientModel = JsonConvert.Deserialize<Model>(modelFromClient);
return await add(clientModel.user);
}
public async Task<IHttpActionResult> add(User user)
{
//Do whatever you need with user
//Save the object in database
user.Save();
}
Related
I am working on my first project as a junior developer, but i also got the task of creating the backend API. This is therefore my first time touching any backend so i almost dont know any c# or .NET.
Frontend: Nextjs, typescript
Backend: .NET 6, c#
The problem:
The users need to be able to upload an image to an artist object. As of now i am only using a string that represents the path where the image is stored in the frontend.
I will figure out how i will recieve the image in the frontend, and i will create an endpoint in the API where i can PUT the image.
The question im trying to ask is how do i do the recieving and storing of the image in the .NET?
Artist entity:
namespace BookingAPI.Entities
{
public class Artist
{
public string Id { get; set; }
public string Name { get; set; } = null!;
public string Description { get; set; }
public string? Image { get; set; }
}
}
CreateArtist in Controller
// POST /events/{eventId}/program/artists
// Create new artist in Program -> Artists
[HttpPost]
[Route("/events/{eventId}/program/artists")]
public async Task<ActionResult<EventArtistDto>> CreateEventArtistAsync(string eventId, CreateEventArtistDto createEventArtistDto)
{
Artist artist = new()
{
Id = Guid.NewGuid().ToString(),
Name = createEventArtistDto.Name,
Description = createEventArtistDto.Description,
Image = createEventArtistDto.Image,
};
await repository.CreateEventArtistAsync(eventId, artist);
return artist.AsDto(); // add return body?
}
CreateArtost in inMemRepository
public async Task CreateEventArtistAsync(string eventId, Artist artist)
{
var eventIndex = events.FindIndex(existingEvent => existingEvent.Id == eventId);
var existingEvent = events[eventIndex];
var artists = existingEvent.EventProgram.Artists;
artists.Add(artist);
await Task.CompletedTask;
}
Please let me know if i should upload more of the code.
Im really not sure even how to start this code, i am of course googling this at the same time, but though i would make my own question as well so that maybe i could learn this the proper way.
UPDATE:
After reading #EminNiftiyev answer i tried this,
but i get errors:
Controller: "Implicitly-typed variables must be initialized"
InMemRepository: "Cannot implicitly convert type 'Microsoft.AspNetCore.Http.IFormFile' to 'string'"
I dont fully understand what i am doing here.
UpdateImageInEventArtistAsync in Controller
//PUT /events/{eventId}/program/artists/{artistId}/image
// Update the Image in the Artist
[HttpPost]
[Route("/events/{eventId}/program/artists/{artistId}/image")]
public async Task<ActionResult<UpdateImageInEventArtistDto>>
UpdateImageInEventArtistAsync(string eventId, string artistId,
UpdateImageInEventArtistDto updateImageInEventArtistDto,
[FromForm] IFormFile file)
{
// Get Program from Event
var program = await repository.GetEventProgramAsync(eventId);
var existingArtist = program.Artists.Where(artist => artist.Id == artistId).SingleOrDefault();
if (existingArtist is null)
{
return NotFound();
}
var byteArrayImage;
using (var stream = new MemoryStream())
{
await file.CopyToAsync(stream);
byteArrayImage = stream.ToArray();
}
existingArtist.Image = Convert.ToBase64String(byteArrayImage);
/* await repository.UpdateEventArtistAsync(eventId, artistId, existingArtist); */
await repository.UpdateImageInEventArtistAsync(eventId, artistId, byteArrayImage);
return NoContent();
}
UpdateImageInEventArtistAsync in InMemRepository
public async Task UpdateImageInEventArtistAsync(string eventId, string artistId, IFormFile file)
{
var eventIndex = events.FindIndex(existingEvent => existingEvent.Id == eventId);
var existingEvent = events[eventIndex];
var artists = existingEvent.EventProgram.Artists;
var existingArtist = artists.Where(artist => artist.Id == artistId).SingleOrDefault();
existingArtist.Image = file;
await Task.CompletedTask;
}
First you create endPoint for Upload Image. That time perfectly choice is use IFormFile. Code like this
[HttpPost]
[Route("api/image/upload")]
public async Task<IActionResult> UploadImage([FromForm] IFormFile file)
{
}
Then you get image like byteArray format. For this you should use Stream
var byteArrayImage;
using (var stream = new MemoryStream())
{
await file.CopyToAsync(stream);
byteArrayimage = stream.ToArray();
}
Here we go. Now yo can get Image like ByteArray format. And Finally you can insert to Database or another source.
That's perfect practice from another way.
I am writing a web app using .NET for the backend and React as client. I want to implement the authorization to a login form and thus made two model (DTOs) to send back to the client. One for the login and register respectively. I have an AccountController class where I am handling the Post and Get requests for the current user (user after logged in).
As mentioned above I have a model class named User and its UserDTO. Inside of that model class, I made some props (eg. username, password, profilePictureURL, email) etc. I want the user to upload an image which in turn will get appended to the profilePictureURL into the request back to the server.
This is my User model:
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
namespace API.Entities
{
public class User : IdentityUser
{
public IFormFile profilePhotoURL {get; set; }
public string Name { get; set; }
}
}
userName, email and phone numbers are being derived from IdentityUser class from .NET.
registerDTO
using Microsoft.AspNetCore.Http;
namespace API.DTOs
{
public class RegisterDTO : LoginDTO
{
public string Email { get; set; }
public string Name { get; set; }
public FormFile profilePhotoURL { get; set; }
public string PhoneNumber { get; set; }
}
}
Inside of AccountController, my POST method for registering is:
[HttpPost("register"), DisableRequestSizeLimit]
public async Task<IActionResult> RegisterUser(RegisterDTO registerDTO)
{
//getting the file from request
var postedProfile = Request.Form.Files[0];
// setting the Uploads folder
var Uploads = Path.Combine(Directory.GetCurrentDirectory(), "Uploads");
if (postedProfile.Length > 0)
{
var fileName = ContentDispositionHeaderValue.Parse(postedProfile.ContentDisposition).FileName.Trim();
var pathToSave = Path.Combine(Uploads, fileName.ToString());
using (var fileStream = new FileStream(pathToSave, FileMode.Create))
{
await postedProfile.CopyToAsync(fileStream);
}
Ok($"File Uploaded successfully");
}
else
{
return BadRequest(new ProblemDetails
{
Title = "400 - Bad Request",
Status = 400,
Detail = "File not uploaded"
});
}
var registeredUser = new User
{
UserName = registerDTO.userName,
Email = registerDTO.Email,
Name = registerDTO.Name,
profilePhotoURL = postedProfile,
PhoneNumber = registerDTO.PhoneNumber
};
var result = await _userManager.CreateAsync(registeredUser, registerDTO.Password);
if (!result.Succeeded)
{
foreach (var Error in result.Errors)
{
ModelState.AddModelError(Error.Code, Error.Description);
}
return ValidationProblem();
}
await _userManager.AddToRoleAsync(registeredUser, "Member");
return StatusCode(201);
}
I want to set the uploaded image into the profilePhotoURL property of my registerDTO class, but when on checking this in swagger, I get the following error:
The JSON value could not be converted to Microsoft.AspNetCore.Http.FormFile. Path: $.profilePhotoURL | LineNumber: 5 | BytePositionInLine: 36.
The above error is in the ModelState errors.
How could I make a method (inside of AccountController's POST request for registering a new user) to upload a file of type IFormFile and then set it inside of the User object?
All suggestions are welcome :)
Your backend was a webapi project, model binding get data from the request body(Json value) by default,but formfile get values from posted form fields.
If you could post a form in your react app,just create a model for the form and add the [FromForm]Attribute
[HttpPost("register"), DisableRequestSizeLimit]
public IActionResult RegisterUser([FromForm] RegisterModel registermodel)
{
return StatusCode(200);
}
I'm trying to post anonymous object via httpclient, however orderId is null and collection empty when it hits controller.
public async Task<Response> CancelOrderAsync(int orderId, ICollection<int> ids)
{
Response result = null;
using (IHttpClient client = HttpClientFactory.CreateHttpClient())
{
var obj = new {OrderId = orderId, Ids = ids};
string json = JsonConvert.SerializeObject(obj);
HttpContent postContent = new StringContent(json, Encoding.UTF8, "application/json");
using (var response = await client.PostAsync($"{url}/admin/cancel", postContent).ConfigureAwait(false))
{
if (response != null && response.IsSuccessStatusCode)
{
...
}
}
}
return result;
}
// Controller
[HttpPost]
[ActionName("cancel")]
public async Task<Response> Cancel(int orderId, ICollection<int> ids)
{
// order is null, collection empty
...
EDIT:
Changed my controller to this for simplicity
[HttpPost]
[ActionName("cancel")]
public async Task<SimpleResponse> Cancel(int orderId)
Via Postman, i'm posting this body:
{
"orderId": "12345"
}
Still, orderId comes in as 0 (zero) ??
The controller action on the server side will need a concrete type to read the entire body of the request
public class Order {
public int OrderId { get; set; }
public int[] Ids { get; set; }
}
This is primarily because the action can only read from the body once.
Update action to...
[HttpPost]
[ActionName("cancel")]
public async Task<Response> Cancel([FromBody]Order order) {
if(ModelState.IsValid) {
int orderId = order.OrderId;
int[] ids = order.Ids;
//...
}
//...
}
the original code used to send the request in the example will work as is, but as mentioned it can be improved.
The HttpClient can do the serialisation for you. See if
var response = await client.PostAsJsonAsync($"{url}/admin/cancel", obj);
works better. Then you don't need to write the serialisation code yourself.
If you still have a problem, use a tool such as Fiddler to monitor the actual request and see what parameter and values are submitted in the request body, to see if they match what's expected by the endpoint.
First, I am not sure if my question reflects my needs, please let me know if that needs to be changed.
What I am doing here is creating functions in DLL application to be called from DotNET outside c#. The issue with my function now is the Model class. Where I am going to use this DLL, I cannot see/use my model class from the outside, therefore how do I change my code to use a string instead of Product model? I still need to send my request as JSON though.
I am working with JSON
I have the following 2 classes:
In Class 1 (SetupWebAPIAsync): Function that puts a product (Model)
public static async Task<ApiResponse> PutProductAsync(string endpoint, Product p)
{
StringContent httpContent = new StringContent(JsonConvert.SerializeObject(p), Encoding.UTF8, "application/json");
string result = "";
HttpResponseMessage response = await client.PutAsync(endpoint, httpContent);
response.EnsureSuccessStatusCode();
result = await response.Content.ReadAsStringAsync();
return new ApiResponse(response.StatusCode, result);
}
In Class 2:
public static ApiResponse PutIn(string user, string password, string endpoint , Product Httpcontent)
{
User = user;
Password = password;
Endpoint = endpoint;
Content = Httpcontent;
ExecutePUTRequest().Wait();
return apiResponse;
}
private static async Task ExecutePUTRequest()
{
SetupWebAPIAsync.SetAPIAuthentication(User, Password);
apiResponse = await SetupWebAPIAsync.PutProductAsync(Endpoint,Content);
}
My Model Class:
public class Product
{
public string id { get; set; }
public string name { get; set; }
public bool inactive { get; set; }
}
ex:
{
"id" : "12",
"name" : "test",
"inactive": false,
}
Now this is how I call my function and it works this way BUT I need to replace product by a string I pass in from my test outside Dll.
Product product = new Product { name = "API_Testing" };
PutIn("user", "pass", "https://localhost/api/product", product);
Well you can't cause your PutIn() method expects an Product Httpcontent as method parameter. instead take a stringified product as JSON and convert that to product and call the main method like
public static ApiResponse PutIn(string user, string password,
string endpoint , string Httpcontent)
{
User = user;
Password = password;
Endpoint = endpoint;
Content = NewtonSoft.Json.JsonConvert.DeserializeObject<Product>(Httpcontent);
ExecutePUTRequest().Wait();
return apiResponse;
}
You can then call it like
string product = "{id : 12,name : API_Testing,inactive: false,}"
PutIn("user", "pass", "https://localhost/api/product", product);
In my WebApi I have a HttpGet and HttpPost method, the get method is working fine and the post method is called but the body content is always null, unless used in a HttpRequestMessage. I tried providing the body content in a string format(preferred datatype) aswell as in a model but neither one of those methods worked. I also tried switching the content type without success. Does anyone know if I'm doing something wrong or how I can easily get the variable data from the HttpRequestMessage, which in the example below is "test".
Method 1:
[System.Web.Http.HttpPost]
[Route("api/v1/AddItem")]
public IHttpActionResult AddItem([FromBody]string filecontent, string companycode)
{
MessageBox.Show(filecontent);
Return Ok("");
}
Method 2 (with model):
[System.Web.Http.HttpPost]
[Route("api/v1/AddItem")]
public IHttpActionResult AddItem([FromBody]ItemXML filecontent, string companycode)
{
MessageBox.Show(filecontent.XMLContent);
Return Ok("");
}
Model:
public class ItemXML
{
public ItemXML(string content)
{
XMLContent = content;
}
public string XMLContent { get; set; }
}
Method 3:
[System.Web.Http.HttpPost]
[Route("api/v1/AddItem")]
public IHttpActionResult AddItem(HttpRequestMessage filecontent, string companycode)
{
var content = filecontent.Content.ReadAsStringAsync().Result;
MessageBox.Show(content);
Return Ok("");
}
Method 3 content string ("test" is the provided value): " content "------WebKitFormBoundarydu7BJizb50runvq0\r\nContent-Disposition: form-data; name=\"filecontent\"\r\n\r\n\"test\"\r\n------WebKitFormBoundarydu7BJizb50runvq0--\r\n" string"
Create a model store data to be sent to server
public class Model {
public string filecontent { get; set;}
public string companycode { get; set;}
}
Update Action
[HttpPost]
[Route("api/v1/AddItem")]
public IHttpActionResult AddItem([FromBody]Model model) {
if(ModelStat.IsValid) {
return Ok(model); //...just for testing
}
return BadRequest();
}
On the client make sure the request is being sent properly. In this case going to use JSON.
public client = new HttpClient();
var model = new {
filecontent = "Hello World",
companycode = "test"
};
var response = await client.PostAsJsonAsync(url, model);
If using another type of client ensure that the data being sent is formatted correctly for the Web API action to accept the request.
Reference Parameter Binding in ASP.NET Web API