File Upload and Download Asp.Net Core Web API - c#

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.

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 make use of IFormFile and attach it to the DTO to be sent to client in ASP.NET Core

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

How to change the content type of httppostedfilebase?

I am using HttpPostedFileBase to upload a file using ASP.NET MVC. I need to post this to a API call and i am getting a error
"StatusCode: 403, Reason Phrase: 'Forbidden', Version: 1.1, Content:
System.Net.Http.StreamContent".
This is because i am passing file content as "application/octet-stream", where as API call wants the content type as "application/vnd.qlik.sense.app".
Many posts in internet says that HttpPostedFileBase is a read only and we cannot change the content type. Can anybody let me know how we can change the content type of HttpPostedFileBase. Is this possible?
here is my code.
[HttpPost]
public ActionResult UploadFile(HttpPostedFile file)
{
if (file != null && file.ContentLength > 0)
{
var fileName = Path.GetFileNameWithoutExtension(file.FileName);
APIcall.Upload(fileName);
}
return RedirectToAction("Index");
}
Found this here how set HttpPostedFileBase ContentType value in runtime
public class MemoryPostedFile : HttpPostedFileBase
{
private readonly byte[] fileBytes;
public MemoryPostedFile(byte[] fileBytes, string fileName = null,string ContentType=null)
{
this.fileBytes = fileBytes;
this.FileName = fileName;
this.ContentType = ContentType;
this.InputStream = new MemoryStream(fileBytes);
}
public override int ContentLength => fileBytes.Length;
public override string FileName { get; }
public override string ContentType { get; }
public override Stream InputStream { get; }
}

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

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

How do I link to downloadable files in ASP.NET MVC?

I am new to ASP.NET MVC and I'am trying to link to downloadable files (.zip, .mp3, .doc, etc).
I have the following view: ProductName
which maps to: http://domain/ProductName
I have a .zip file that must map to URL http://domain/ProductName/Product.zip
Questions
Where do I place this .zip file in the MVC folder structure?
How do I add link to this .zip file in MVC? Is there a Url.* method that do this?
You can use FilePathResult or Controller.File method.
protected internal virtual FilePathResult File(string fileName, string contentType, string fileDownloadName) {
return new FilePathResult(fileName, contentType) { FileDownloadName = fileDownloadName };
}
Sample code action method.
public ActionResult Download(){
return File(fileName,contentType,downloadFileName);
}
Hope this code.
The following class adds a file DownloadResult to your program:
public class DownloadResult : ActionResult
{
public DownloadResult()
{
}
public DownloadResult(string virtualPath)
{
this.VirtualPath = virtualPath;
}
public string VirtualPath { get; set; }
public string FileDownloadName { get; set; }
public override void ExecuteResult(ControllerContext context)
{
if (!String.IsNullOrEmpty(FileDownloadName))
{
context.HttpContext.Response.AddHeader("content-disposition",
"attachment; filename=" + this.FileDownloadName);
}
string filePath = context.HttpContext.Server.MapPath(this.VirtualPath);
context.HttpContext.Response.TransmitFile(filePath);
}
}
To call it, do something like this in your controller method:
public ActionResult Download(string name)
{
return new DownloadResult
{ VirtualPath = "~/files/" + name, FileDownloadName = name };
}
Note the virtual path, which is a files directory in the root of the site; this can be changed to whatever folder you want. This is where you put your files for download.
Check this tutorial about Writing A Custom File Download Action Result For ASP.NET MVC
Another simplified example using FileResult as takepara suggested above.
Note I created a HelperController.cs class.
In your view...
#Html.ActionLink("Link Description", "Download", "Helper", new { fileName = "downloaded_file_name.ext", path = "root path location to your file" }, null)
Controller action...
public FileResult Download(string fileName, string path)
{
var webRootPath = Server.MapPath("~");
var documentationPath = Path.GetFullPath(Path.Combine(webRootPath, path));
var filePath = Path.GetFullPath(Path.Combine(documentationPath, fileName));
return File(filePath, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
}

Categories

Resources