Send raw JSON and file in the same web api 2 endpoint - c#

I was wondering whether it is possible to send a file (which I only want ".pdf", ".jpg" or ".png") along with raw JSON.
All my endpoints so far send raw JSON (which I'm testing via Postman as the frontend does not exist yet), the intention is that the sending of form data will be sent using Angular Js. I don't know Angular Js yet, so I can't imagine how this will work.
The signature of the endpoint in question looks like this:
[Route("Post")]
[CustomAuthorize(Roles = "User, Admin")]
[ValidateJWT]
public async Task<IHttpActionResult> Post(HttpRequestMessage request, SalesOrderViewModel orderData)
The view model is just a c# class with loads of string properties that the model binder converts from JSON.
I would like to know whether sending raw JSON and a file that the user will select is possible in the same endpoint with Web API 2.
Is it?
Thanks in advance.

You can't direct with a post as aplication/json, still you can do it with multiple form fields (as form data), file + data, where the value of data can be a JSON.
I'm not recommending this method but does the trick:
public async Task<IHttpActionResult> Post()
{
if (!Request.Content.IsMimeMultipartContent())
{
Request.CreateResponse(HttpStatusCode.UnsupportedMediaType);
}
//load in a memory stream or in azure blob storage
var uploadFolder = "~/App_Data/FileUploads"; // to demonstrate the upload so please don't comment about where I'm saving the file, don't recommend this under no circumstance
var root = HttpContext.Current.Server.MapPath(uploadFolder);
Directory.CreateDirectory(root);
var provider = new MultipartFormDataStreamProvider(root);
var result = await Request.Content.ReadAsMultipartAsync(provider);
if (result.FileData.FirstOrDefault() == null)
{
return BadRequest("No import file was attached");
}
var uploadedFileInfo = new FileInfo(result.FileData.First().LocalFileName);
var model = result.FormData["model"];
if (model == null)
{
return BadRequest("Model is missing");
}
var parameters = JsonConvert.DeserializeObject<Coords>(model);
var byteArray = File.ReadAllBytes(uploadedFileInfo.FullName);
//..process the bytes
//..process json passed in headers
}
And the model:
public class Coords
{
public Cord[] cords { get; set; }
}
public class Cord
{
public int x { get; set; }
public object y { get; set; }
}
Postman call:

Edit: Just checked you don't have to use IEnumerable<byte>. Using byte[] works fine.
WebApi supports deserializing JSON array to IEnumerable<T>, so that you can receive bytes by declaring with IEnumerable<byte>.
The following example will show how to upload image:
public class ImageModel
{
public string Name { get; set; }
public IEnumerable<byte> Bytes { get; set; }
}
In your controller. Writing image to disk:
private string WriteImage(byte[] arr)
{
var filename = $#"images\{DateTime.Now.Ticks}.";
using (var im = Image.FromStream(new MemoryStream(arr)))
{
ImageFormat frmt;
if (ImageFormat.Png.Equals(im.RawFormat))
{
filename += "png";
frmt = ImageFormat.Png;
}
else
{
filename += "jpg";
frmt = ImageFormat.Jpeg;
}
string path = HttpContext.Current.Server.MapPath("~/") + filename;
im.Save(path, frmt);
}
return $#"http:\\{Request.RequestUri.Host}\{filename}";
}
HttpContext.Current.Server.MapPath("~/") will give the internal path of server running. Request.RequestUri.Host returns the hostname.
public IHttpActionResult UploadImage(ImageModel model)
{
var imgUrl = WriteImage(model.Bytes.ToArray());
// Some code
}
In HTML:
<input type="file" id="imageFile"/>
Upload method for AngularJS:
$scope.upload = function () {
var file = document.getElementById("imageFile").files[0];
var r = new FileReader();
r.onloadend = function (e) {
var arr = Array.from(new Uint8Array(e.target.result));
var uploadData = {
Name: "Name of Image",
Bytes: arr
}
console.log(uploadData);
$http.post('api/Uploader/UploadImage', uploadData)
.then(
function (response) {
console.log(response);
},
function (reason) {
console.log(reason);
})
}
r.readAsArrayBuffer(file);
}

Related

.NET 6 API - Upload image from frontend to database (UPDATED)

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.

How to send multiple file upload over the api which type is MultipartFileStreamProvider.FileData?

I have this file upload (multiple)
<input asp-for="MyDocuments" type="file" multiple title="Upload Files..." />
This is my C# model
public class FormModel
{
public string Name { get; set; }
public IFormFile[] MyDocuments { get; set; }
}
This is how I issue http request. I am using Flurl
string endPoint = "/Api/Abc/";
string url = string.Format("{0}{1}{2}", baseUrl, endPoint, clientId);
var response = await url.SetQueryParams(new { id, code })
.PostMultipartAsync(mp => mp
.AddStringParts(formModel)
);
When I debug it, I can see that formModel has all the payload data. In my endpoint, I can get Name value but provider.FileData is empty.
string root = Path.GetTempPath();
var provider = new MultipartFormDataStreamProvider(root);
// Read the form data.
await Request.Content.ReadAsMultipartAsync(provider);
var name = provider.FormData["Name"];
foreach (MultipartFileData file in provider.FileData)
{
}
I notice that the type for provider.FileData is MultipartFileStreamProvider.FileData. But I am not sure how to convert IFormFile to MultipartFileStreamProvider.FileData
Added:
I tried to change from IFormFile[] to IFormFile and I can get it work by using .AddFile method in Flurl. But, I need to upload multiple files. So I still need IFormFile[]

File Upload and Download Asp.Net Core Web API

I have to create a web API for file management which are file upload, download, delete in ASP.NET Core. The requirement is this that the file will be saved to the disk and the path, filename, UniqueId will be saved in the database. The Entity Model that I have created is this:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace FileManagerAPI.Model
{
public class FileDetail
{
public Guid Id { get; set; }
public DateTime? DateEntered { get; set; }
public bool? Deleted { get; set; }
public string DocumentName { get; set; }
public string DocId { get; set; }
public string DocType { get; set; }
public string DocUrl { get; set; }
}
}
Only selected types of files(pdf, png, jpg, jpeg) can be uploaded.
I have read a lot of documents but I couldn't make it work. Also, I have to save the files outside the project root directory. How can I implement this? Also I am using ASP.Net Core 3.1!
Here is what I have done to upload a file in my Controller.
namespace FileManagerAPI.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class FilesController : ControllerBase
{
private readonly FileDbContext _context;
private readonly IHostingEnvironment _env;
public FilesController(FileDbContext context, IHostingEnvironment env)
{
_context = context;
_env = env;
}
[HttpPost]
public IActionResult Upload(IList<IFormFile>files)
{
//either you can pass the list of files in the method or you can initialize them inside the method like the commented line below
//var files = HttpContext.Request.Form.Files;
FileDetail fileDetail = new FileDetail();
foreach (var file in files)
{
var fileType = Path.GetExtension(file.FileName);
//var ext = file.;
if (fileType.ToLower() == ".pdf" || fileType.ToLower() == ".jpg" || fileType.ToLower() == ".png" || fileType.ToLower() == ".jpeg")
{
var filePath = _env.ContentRootPath;
var docName = Path.GetFileName(file.FileName);
if (file != null && file.Length > 0)
{
fileDetail.Id = Guid.NewGuid();
fileDetail.DocumentName = docName;
fileDetail.DocType = fileType;
fileDetail.DocUrl = Path.Combine(filePath, "Files", fileDetail.Id.ToString() + fileDetail.DocType);
using (var stream = new FileStream(fileDetail.DocUrl, FileMode.Create))
{
file.CopyToAsync(stream);
}
_context.Add(fileDetail);
_context.SaveChangesAsync();
}
else
{
return BadRequest();
}
}
}
return Ok();
}
[HttpGet]
public IActionResult Download(Guid id)
{
var fileDetail = _context.FileDetail
.Where(x => x.Id == id)
.FirstOrDefault();
if(fileDetail != null)
{
System.Net.Mime.ContentDisposition cd = new System.Net.Mime.ContentDisposition
{
FileName = fileDetail.DocumentName,
Inline = false
};
Response.Headers.Add("Content-Disposition", cd.ToString());
//get physical path
var path = _env.ContentRootPath;
var fileReadPath = Path.Combine(path, "Files", fileDetail.Id.ToString() + fileDetail.DocType);
var file = System.IO.File.OpenRead(fileReadPath);
return File(file, fileDetail.DocType);
}
else
{
return StatusCode(404);
}
}
}
For upload file - you should use interface IFormFile in your command and save Stream from that interface to eg. array of bytes. (Remeber - sending file should be send by HTTP Form Method). Before save you should check what is mime type and wheresome write information about file eg. in database.
For download file - you can use Method File in Controller. First arg of that method is Stream/Array of bytes/Physic path to file, second is mime/type.
To saving file outside Project Root can be sometimes probaly. Lot of external servers not shar that posibility. In my opinion should you save file in eg. Azure Blobs or simply in wwwroot in application.
If you are passing the file back to your controller using HttpPostedFileBase, you can adapt the following code to suit your needs.
string path = Path.Combine(Server.MapPath("~/Path/To/Desired/Folder"), file.FileName);
file.SaveAs(path);
file is a parameter of type HttpPostedFileBase, and is passed back to the controller via a HttpPost Method.
Just make sure that your program has the correct permissions to access the folder you desire.

Can't call Web Api by Alamofire.upload multipartFormData

I have an Windows WEB API with the following method:
public async Task<IHttpActionResult> SaveContract([FromBody] ModelDTO model)
{
string custName = model.CustomerName;
...
}
The Model I want looks like this:
public class ModelDTO
{
public int CustomerNumber { set; get; }
public string CustomerName { set; get; }
public string CustomerMail { set; get; }
public string imageDataBase64 { set; get; }
}
I want to call the API with my iOS App (Swift 4) with Alamofire 4.7.2
My dev server has a self-signed certificate. So I need to disable the evaluation.
let defaultManager: Alamofire.SessionManager = {
let serverTrustPolicies: [String: ServerTrustPolicy] = [
"devserver": .disableEvaluation
]
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = Alamofire.SessionManager.defaultHTTPHeaders
return Alamofire.SessionManager(
configuration: configuration,
serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies))
}()
let webApi: String = "https://devserver:7208/api/KP/SaveContract"
let data = UIImageJPEGRepresentation(photoImageView.image!,1) //got the Data form an image view
var imgString: String = ""
imgString = data.base64EncodedString()
let Param = Parameters = [
"CustomerNumber": 1,
"CustomerName": "Test Name",
"CustomerMail": "test#test.com",
"imageDataBase64": imgString]
defaultManager.upload(
multipartFormData: { multipartFormData in
for (key, value) in contAsJsonParam {
multipartFormData.append("\(value)".data(using: .utf8)!, withName:key)
}
},
to: webApi,
encodingCompletion: { encodingResult in
switch encodingResult {
case .success(let upload, _, _):
upload.responseJSON { response in
debugPrint(response)
//lbl sichtbar machen
}
case .failure(let encodingError):
print(encodingError)
}
})
Call the api without image with Alamofire.request works, but with image request, it dosen't work. (bad ssl error)
So I try the upload method, but upload dosen't work in any way (with or without image string)
If I call the Api with Alamofire.upload I got a system.net.http.unsupportedmediatypeexception
"No MediaTypeFormatter is available to read an object of type
'ModelDTO' from content with media type 'multipart/form-data'."
I try to make the upload class as json by at "headers: Content-Type:application/json"
but no effect.
I try to fix it by putting
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("multipart/form-data"));
in the WebApiConfig. Then i got an other error
I got an NullReferenceException in the api at the line "string custName = model.CustomerName;"
You can use this code. I have tested this code with multipart data. It's working fine for me.
let url = "https://devserver:7208/api/KP/SaveContract"
//imp data need to be dynamic
let parameters: NSMutableDictionary = ["userId":"1123",
"caption":"hello how are you",
"keyword":"First Post",
"askPrice":"12345",
"postScope":"1",
"commentStatus":"1",
"gender":"male",
"email":"asd#asd.com"
]
var dictionaryHeaders = [String : String]()
dictionaryHeaders = ["Content-Type": "application/x-www-form-urlencoded" ]
Alamofire.upload(multipartFormData: { (multipartFormData) in
for (key, value) in parameters {
multipartFormData.append("\(value)".data(using: String.Encoding.utf8)!, withName: key as! String)
}
self.postImage = UIImage(named:"YOUR IMAGE NAME")
if let data = UIImageJPEGRepresentation(self.postImage,1) {
multipartFormData.append(data, withName: "postPic", fileName: "image.png", mimeType: "image/png")
}
}, usingThreshold: UInt64.init(), to: url, method: .post, headers: dictionaryHeaders ) { (result) in
switch result{
case .success(let upload, _, _):
upload.responseJSON{ response in
print(response)
}
case .failure(let error):
print("Error in upload: \(error.localizedDescription)")
}
}

C# Cast FormData to an Object

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();
}

Categories

Resources