I want to make an Post api call. the get method works without any problems, but the post call doesnt find the route. idk why...
Here's the Controller
[Authorize(Roles = AppRoles.Supervisor)]
[Route("api/[controller]/[action]")]
[ApiController]
public class SupportUserManagementController : ControllerBase
{
private readonly SupportUserService _supportUsersService;
public SupportUserManagementController(SupportUserService supportUserService)
{
this._supportUsersService = supportUserService;
}
// GET: api/GetSupportUsersListAsync
[HttpGet]
[ActionName("Test2")]
[Authorize(Roles = AppRoles.User)]
[ProducesResponseType(typeof(IEnumerable<ScopeSupportUsers>), StatusCodes.Status200OK)]
[SwaggerOperation("GetSupportUsers")]
public async Task<IEnumerable<ScopeSupportUsers>> GetSupportUsers(int id)
{
var result = await _supportUsersService.GetSupportUsersListAsync(id);
return result;
}
[HttpPost]
[ActionName("Test3")]
[Authorize(Roles = AppRoles.User)]
[ProducesResponseType(typeof(Guid?), StatusCodes.Status200OK)]
[SwaggerOperation("CreateSupportUsers")]
public async Task<Guid?> CreateSupportUsers(int id, ScopeSupportUsers user)
{
var existingUser = await GetSupportUsers(id);
var matches = existingUser.Where(u => u.Name == user.Name);
if (matches.Count() != 0)
{
return Guid.Empty;
}
var resultGuid = await _supportUsersService.CreateSupportUserAsync(id, user);
if (resultGuid != null)
{
var resultCertificate = await SetCertificate_1Async(id, user, (Guid)resultGuid);
if (resultCertificate == true)
return resultGuid;
}
return null;
}
Why does this work:
using var client = await httpClientHelper.GetHttpClientWithAccessToken();
string uri = $"{_serviceClientOptions.Value.BetriebstoolServiceAddress}/api/SupportUserManagement/Test2?id={selectedScope}";
supportUserResult = await client.GetFromJsonAsync<IEnumerable<ScopeSupportUsers>>(uri);
And this doesnt work?
using var client = await httpClientHelper.GetHttpClientWithAccessToken();
string uri = $"{_serviceClientOptions.Value.BetriebstoolServiceAddress}/api/SupportUserManagement/Test3?id={selectedScopeToCopyTo}";
var response = await client.PostAsJsonAsync<ScopeSupportUsers>(uri , user);
If i am debugging the GET works fine but at the POST Method it doesnt find the "way" to the controller"
This Breakpoint never reached :(
so in the project a user could upload, download, delete, preview and search for (in real time) a file. The files are stored in a folder called "Uploads" inside of the wwwroot folder. As of now, the code has absolute pathing, but the project will need to be on different computers so I need relative pathing, can anyone help me out with a sample syntax?
[HttpPost]
public async Task<IActionResult> Index(IFormFile fifile, string category)
{
string path = #"C:\Users\panara5\source\repos\Proiect2PracticaClona\Proiect2PracticaClona\wwwroot\Uploads\";
var fisave = Path.Combine(path, fifile.FileName);
var stream = new FileStream(fisave, FileMode.Create);
await fifile.CopyToAsync(stream);
Files fileModel=new Files()
{
info = fifile.FileName,
category = category
};
_fileRepository.AddFile(fileModel);
stream.Close();
files = _fileRepository.GetFileList();
return RedirectToAction("Index",files);
}
public IActionResult Index(string id)
{
files = _fileRepository.GetFileList();
if (id == null)
return View(files);
List<Files> sortedfiles = new List<Files>();
foreach (var item in files)
{
if (item.category == id)
{
sortedfiles.Add(item);
}
}
return View(sortedfiles);
}
public IActionResult Delete(string filedel)
{
filedel = Path.Combine("~/Uploads/", filedel);
FileInfo fi = new FileInfo(filedel);
if (fi != null)
{
System.IO.File.Delete(filedel);
fi.Delete();
}
return RedirectToAction("Index");
}
also i get a null reference when trying to delete
I believe you want IWebHostEnvironment.WebRootPath
You need to use Dependency Injection to get access to it, but after that, you can just reference and Path.Combine. See below:
public class FileController : Controller
{
private readonly IWebHostEnvironment environment;
public FileController(IWebHostEnvironment env)
{
this.environment = env;
}
private string UploadFilePath() => Path.Combine(environment.WebRootPath, "uploads");
}
This is a question I'll answer myself. I've spent a couple of hours trying to make it work, based on the example provied here: https://github.com/telerik/ui-for-aspnet-mvc-examples/tree/master/editor/database-image-browser/DatabaseImageBrowser
Since I don't maintain a blog, this is my way of documenting this, in case others face the same use case and this might save them some time
The problem is: How do I implement an Imagebrowser, that does not work with a local folder, but with a database. The sample provided by Telerik is working with virtual folders, stored in one table and images linked in a seperate one, that are linked with a Folder Id. Since I did not want to use folders, I needed to find a way to work around this. Also: The IImageBrowserController only offers a synchronous interface, which made it unsuitable for async operations:
public interface IImageBrowserController : IFileBrowserController
{
IActionResult Thumbnail(string path);
}
public interface IFileBrowserController
{
ActionResult Create(string path, FileBrowserEntry entry);
ActionResult Destroy(string path, FileBrowserEntry entry);
JsonResult Read(string path);
ActionResult Upload(string path, IFormFile file);
}
The second problem is: How do you convert the Image read path from a virtual path .Image("~/Content/UserFiles/Images/{0}") to a mvc route
And lastly, How do you implement a custom controller or Razor page, so you don't need to use virtual folders on Asp.Net Core.
First of all, create an interface that is suitable for async operations:
public interface IImageBrowserControllerAsync
{
Task<IActionResult> Create(string name, FileBrowserEntry entry);
Task<IActionResult> Destroy(string name, FileBrowserEntry entry);
Task<IActionResult> Image(string path);
Task<JsonResult> Read(string path);
Task<IActionResult> Thumbnail(string path);
Task<IActionResult> Upload(string name, IFormFile file);
}
Next up, create the controller implementation. I'll omit a few of the methods, so I don't waste precious reading time. The implementation is similar to the provided methods:
public class ImageBrowserController : ControllerBase, IImageBrowserControllerAsync
{
private IImageRepository _repo;
private const int ThumbnailHeight = 80,
ThumbnailWidth = 80;
public ImageBrowserController(IImageRepository repo)
{
_repo = repo;
}
[Route("Image")]
public async Task<IActionResult> Image(string path)
{
var image = await _repo.GetByName(path);
if (image != null)
{
return File(image.Data, image.ContentType);
}
return NotFound("Errormessage");
}
//read all images, when the widget loads
[Route("Read")]
public async Task<JsonResult> Read(string path)
{
var images = await _repo.Get(); // do not return the image data. it is not
//needed and will clog up your resources
var fbe = images.Select(x => new FileBrowserEntry
{
Name = x.Name,
EntryType = FileBrowserEntryType.File
});
return new JsonResult(fbe);
}
//Create thumbnail using SixLabors.Imagesharp library
[Route("Thumbnail")]
public async Task<IActionResult> Thumbnail(string path)
{
var image = await _repo.GetByName(path);
if (image != null)
{
var i = SixLabors.ImageSharp.Image
.Load(image.Data);
i.Mutate(ctx => ctx.Resize(ThumbnailWidth, ThumbnailHeight));
using (var ms = new MemoryStream())
{
i.SaveAsJpeg(ms);
return File(ms.ToArray(), image.ContentType);
}
}
return NotFound();
}
[Route("Upload")]
public async Task<IActionResult> Upload(string name, IFormFile file)
{
if (file == null || file.Length == 0) return BadRequest();
using (var ms = new MemoryStream())
{
file.CopyTo(ms);
var img = new Entities.Image
{
Name = file.FileName,
ContentType = file.ContentType,
Data = ms.ToArray()
};
await _repo.CreateImage(img);
return Ok();
}
}
}
And here is the Imagebrowser / Editor config:
#(Html.Kendo().Editor()
.Name("editor")
.HtmlAttributes(new { style = "width: 100%;height:440px
.Tools(tools => tools
.Clear()
/*omitted config*/
)
.ImageBrowser(ib => ib
//use actionmethod, controller, route values format
.Image("Image", "ImageBrowser", new {path = "{0}"})
.Read("Read", "ImageBrowser") // path can be null if you don't use folders
.Destroy("Destroy", "ImageBrowser")
.Upload("Upload", "ImageBrowser")
.Thumbnail("Thumbnail", "ImageBrowser"))
)
Whoever reads this: I hope this example will help you save some time in implementing this on Asp.Net Core.
Important: When reading all images on load, do not return the byte[]. Kendo only wants a FileBrowserEntry, with a name and a type property.
I strongly advise to implement caching here. Creating thumbnails for dozens or hundreds of images on each page load, will put a huge strain on your infrastructure.
using this question I came up with this code to save a file
[HttpPost]
public IActionResult Upload(string office, IFormFile file)
{
if (file.Length > 0) {
var filePath = Path.GetFullPath(Path.Combine(_environment.WebRootPath,
_configuration["SydneyFloorplanPath"]));
using (var fileStream = new FileStream(filePath, FileMode.Create)) {
file.CopyTo(fileStream);
}
}
return RedirectToAction("Index", new{office = office});
}
But I get a super annoying issue when instantiating FileStream:
An exception of type 'System.IO.DirectoryNotFoundException' occurred in System.Private.CoreLib.dll but was not handled in user code: 'Could not find a part of the path 'C:\images\Floorplan\sydney.pdf'.'
But That's not the path that I need. The Images folder sits in my project under wwwroot\images...
what is the point of WebRootPath if it doesn't actually give me a usable, relative path?
Note that _environment is an injected IHostingEnvironment
I also noticed that if I call
var two = _environment.WebRootPath;
the varaible contains the full path... "C:\\Users\\bassie\\source\\repos\\TFS\\DSSTools\\wwwroot"
But after calling Path.Combine, I suddenly have "C:\\images\\Floorplan\\sydney.pdf" ... why?
Edit:
_environment is a IHostingEnvironment, eg:
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
namespace AspNetCorePathMapping
{
public class HomeController : Controller
{
private readonly IHostingEnvironment _hostingEnvironment;
public HomeController(IHostingEnvironment hostingEnvironment)
{
_hostingEnvironment = hostingEnvironment;
}
public ActionResult Index()
{
string webRootPath = _hostingEnvironment.WebRootPath;
string contentRootPath = _hostingEnvironment.ContentRootPath;
return Content(webRootPath + "\n" + contentRootPath);
}
}
}
I managed to get this working with
[HttpPost]
public IActionResult Upload(string office, IFormFile file)
{
var webRootPath = _environment.WebRootPath;
var floorPlanPath = _configuration["SydneyFloorplanPath"];
if (file.Length > 0) {
var filePath1 = Path.Combine(floorPlanPath,webRootPath.ReplaceFirst("/", ""));
using (var fileStream = new FileStream(filePath1, FileMode.Create)) {
file.CopyTo(fileStream);
}
}
return RedirectToAction("Index", new{office = office});
}
Where ReplaceFirst is a simple StringExtension
public static string ReplaceFirst(this string text, string search, string replace)
{
int pos = text.IndexOf(search);
if (pos < 0)
{
return text;
}
return $"{text.Substring(0, pos)}{replace}{text.Substring(pos + search.Length)}";
}
So it seems the leading / in webRootPath was breaking my Path.Combine call
How can I upload a list of files (images) and json data to ASP.NET Core Web API controller using multipart upload?
I can successfully receive a list of files, uploaded with multipart/form-data content type like that:
public async Task<IActionResult> Upload(IList<IFormFile> files)
And of course I can successfully receive HTTP request body formatted to my object using default JSON formatter like that:
public void Post([FromBody]SomeObject value)
But how can I combine these two in a single controller action? How can I upload both images and JSON data and have them bind to my objects?
Simple, less code, no wrapper model
There is simpler solution, heavily inspired by Andrius' answer. By using
the ModelBinderAttribute you don't have to specify a model or binder provider. This saves a lot of code. Your controller action would look like this:
public IActionResult Upload(
[ModelBinder(BinderType = typeof(JsonModelBinder))] SomeObject value,
IList<IFormFile> files)
{
// Use serialized json object 'value'
// Use uploaded 'files'
}
Implementation
Code behind JsonModelBinder (see GitHub or use NuGet package):
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ModelBinding;
public class JsonModelBinder : IModelBinder {
public Task BindModelAsync(ModelBindingContext bindingContext) {
if (bindingContext == null) {
throw new ArgumentNullException(nameof(bindingContext));
}
// Check the value sent in
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult != ValueProviderResult.None) {
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
// Attempt to convert the input value
var valueAsString = valueProviderResult.FirstValue;
var result = Newtonsoft.Json.JsonConvert.DeserializeObject(valueAsString, bindingContext.ModelType);
if (result != null) {
bindingContext.Result = ModelBindingResult.Success(result);
return Task.CompletedTask;
}
}
return Task.CompletedTask;
}
}
Example request
Here is an example of a raw http request as accepted by the controller action Upload above.
A multipart/form-data request is split into multiple parts each separated by the specified boundary=12345. Each part got a name assigned in its Content-Disposition-header. With these names default ASP.Net-Core knows which part is bound to which parameter in the controller action.
Files that are bound to IFormFile additionally need to specify a filename as in the second part of the request. Content-Type is not required.
Another thing to note is that the json parts need to be deserializable into the parameter types as defined in the controller action. So in this case the type SomeObject should have a property key of type string.
POST http://localhost:5000/home/upload HTTP/1.1
Host: localhost:5000
Content-Type: multipart/form-data; boundary=12345
Content-Length: 218
--12345
Content-Disposition: form-data; name="value"
{"key": "value"}
--12345
Content-Disposition: form-data; name="files"; filename="file.txt"
Content-Type: text/plain
This is a simple text file
--12345--
Testing with Postman
Postman can be used to call the action and test your server side code. This is quite simple and mostly UI driven. Create a new request and select form-data in the Body-Tab. Now you can choose between text and file for each part of the reqeust.
I'm working with Angular 7 on the front-end, so I make use of the FormData class, which allows you to append strings or blobs to a form. They can be pulled out of the form in the controller action using the [FromForm] attribute. I add the file to the FormData object, and then I stringify the data I wish to send together with the file, append it to the FormData object, and deserialize the string in my controller action.
Like so:
//front-end:
let formData: FormData = new FormData();
formData.append('File', fileToUpload);
formData.append('jsonString', JSON.stringify(myObject));
//request using a var of type HttpClient
http.post(url, formData);
//controller action
public Upload([FromForm] IFormFile File, [FromForm] string jsonString)
{
SomeType myObj = JsonConvert.DeserializeObject<SomeType>(jsonString);
//do stuff with 'File'
//do stuff with 'myObj'
}
You now have a handle on the file and the object. Note that the name you provide in the params list of your controller action must match the name you provide when appending to the FormData object on the front-end.
Apparently there is no built in way to do what I want. So I ended up writing my own ModelBinder to handle this situation. I didn't find any official documentation on custom model binding but I used this post as a reference.
Custom ModelBinder will search for properties decorated with FromJson attribute and deserialize string that came from multipart request to JSON. I wrap my model inside another class (wrapper) that has model and IFormFile properties.
IJsonAttribute.cs:
public interface IJsonAttribute
{
object TryConvert(string modelValue, Type targertType, out bool success);
}
FromJsonAttribute.cs:
using Newtonsoft.Json;
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class FromJsonAttribute : Attribute, IJsonAttribute
{
public object TryConvert(string modelValue, Type targetType, out bool success)
{
var value = JsonConvert.DeserializeObject(modelValue, targetType);
success = value != null;
return value;
}
}
JsonModelBinderProvider.cs:
public class JsonModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (context.Metadata.IsComplexType)
{
var propName = context.Metadata.PropertyName;
var propInfo = context.Metadata.ContainerType?.GetProperty(propName);
if(propName == null || propInfo == null)
return null;
// Look for FromJson attributes
var attribute = propInfo.GetCustomAttributes(typeof(FromJsonAttribute), false).FirstOrDefault();
if (attribute != null)
return new JsonModelBinder(context.Metadata.ModelType, attribute as IJsonAttribute);
}
return null;
}
}
JsonModelBinder.cs:
public class JsonModelBinder : IModelBinder
{
private IJsonAttribute _attribute;
private Type _targetType;
public JsonModelBinder(Type type, IJsonAttribute attribute)
{
if (type == null) throw new ArgumentNullException(nameof(type));
_attribute = attribute as IJsonAttribute;
_targetType = type;
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext));
// Check the value sent in
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult != ValueProviderResult.None)
{
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
// Attempt to convert the input value
var valueAsString = valueProviderResult.FirstValue;
bool success;
var result = _attribute.TryConvert(valueAsString, _targetType, out success);
if (success)
{
bindingContext.Result = ModelBindingResult.Success(result);
return Task.CompletedTask;
}
}
return Task.CompletedTask;
}
}
Usage:
public class MyModelWrapper
{
public IList<IFormFile> Files { get; set; }
[FromJson]
public MyModel Model { get; set; } // <-- JSON will be deserialized to this object
}
// Controller action:
public async Task<IActionResult> Upload(MyModelWrapper modelWrapper)
{
}
// Add custom binder provider in Startup.cs ConfigureServices
services.AddMvc(properties =>
{
properties.ModelBinderProviders.Insert(0, new JsonModelBinderProvider());
});
Following the excellent answer by #bruno-zell, if you have only one file (I didn't test with an IList<IFormFile>) you can also just declare your controller as this :
public async Task<IActionResult> Create([FromForm] CreateParameters parameters, IFormFile file)
{
const string filePath = "./Files/";
if (file.Length > 0)
{
using (var stream = new FileStream($"{filePath}{file.FileName}", FileMode.Create))
{
await file.CopyToAsync(stream);
}
}
// Save CreateParameters properties to database
var myThing = _mapper.Map<Models.Thing>(parameters);
myThing.FileName = file.FileName;
_efContext.Things.Add(myThing);
_efContext.SaveChanges();
return Ok(_mapper.Map<SomeObjectReturnDto>(myThing));
}
Then you can use the Postman method shown in Bruno's answer to call your controller.
I had a similar issue and I solved the problem by using [FromForm] attribute and FileUploadModelView in the function as follows:
[HttpPost("Save")]
public async Task<IActionResult> Save([FromForm] ProfileEditViewModel model)
{
return null;
}
I wanted to do the same using Vue frontend and .net core api. But for some weird reason IFormFile always returned null. So then I had to change it to IFormCollection and got it sorted out. Here is the code for anyone facing the same issue :)
public async Task<IActionResult> Post([FromForm]IFormCollection files)
An updated version for .net 5 based on #bruno-zell 's answer with added support to multiple files
using System;
using System.Collections;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Options;
public class JsonModelBinder : IModelBinder
{
private readonly JsonOptions _jsonOptions;
public JsonModelBinder(IOptions<JsonOptions> jsonOptions)
{
_jsonOptions = jsonOptions.Value;
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
// Check the value sent in
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult != ValueProviderResult.None)
{
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
string toSerialize;
// Attempt to convert the input value
if (typeof(IEnumerable).IsAssignableFrom(bindingContext.ModelType))
{
toSerialize = "[" + string.Join<string>(',', valueProviderResult.Values) + "]";
}
else
{
toSerialize = valueProviderResult.FirstValue;
}
var result = JsonSerializer.Deserialize(toSerialize, bindingContext.ModelType, _jsonOptions.JsonSerializerOptions);
if (result != null)
{
bindingContext.Result = ModelBindingResult.Success(result);
return Task.CompletedTask;
}
}
return Task.CompletedTask;
}
}
You don't need "JsonModelBinder" and other custom stuff, I have model
public class UpdateAdminProfileInfoRequest
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Mobile { get; set; }
public IFormFile Image { get; set; }
}
and in Controller action method only one argument
[FromForm]UpdateAdminProfileInfoRequest request
and everything works fine.
If you need multiple file upload just change
IFormFile
with
List<IFormFile> files {get; set}
Be careful I don't know why but currently I use .net6 and nor JsonProperty nor JsonPropertyName don't work on IFormFile, if you decorate Image property with
[JsonProperty("imageFile")]
attribute or something like this asp.net don't map client "imageFile" field to "Image" property.
I am not sure if you can do the two things in a single step.
How I have achieved this in the past is by uploading the file through ajax and returning the file url back in the response and then pass it along with post request to save the actual record.
I had a similar problem when posting from angular to asp core api.
Chrome:
Form Data
------WebKitFormBoundarydowgB6BX0wiwKeOk
Content-Disposition: form-data; name="file1"
undefined
------WebKitFormBoundarydowgB6BX0wiwKeOk
Content-Disposition: form-data; name="file2"
undefined
------WebKitFormBoundarydowgB6BX0wiwKeOk
Content-Disposition: form-data; name="reportData"; filename="blob"
Content-Type: application/json
{"id":2,"report":3,"code":"XX0013","business":"01","name":"Test","description":"Description"}
------WebKitFormBoundarydowgB6BX0wiwKeOk--
Here is how I do it:
I use reportData as an uploaded file data, then I read the file's contents.
[HttpPost]
public async Task<IActionResult> Set([FromForm] IFormFile file1, [FromForm] IFormFile file2, [FromForm] IFormFile reportData)
{
try
{
ReportFormModel.Result result = default;
if (reportData != null)
{
string reportJson = await reportData.ReadFormFileAsync();
ReportFormModel.Params reportParams = reportJson.JsonToObject<ReportFormModel.Params>();
if (reportParams != null)
{
//OK
}
}
return Ok(result);
}
catch (Exception ex)
{
return BadRequest();
}
}
public static class Utilities
{
public static async Task<string> ReadFormFileAsync(this IFormFile file)
{
if (file == null || file.Length == 0)
{
return await Task.FromResult((string)null);
}
using var reader = new StreamReader(file.OpenReadStream());
return await reader.ReadToEndAsync();
}
}
Although this way is not appreciated, but it worked.
I'm just adding my two cents.
The Angular app function for uploading file with json
uploadFileWithJson = (files) => {
if (files.length === 0) {
return;
}
let fileToUpload = <File>files[0];
const formData = new FormData();
formData.append('file', fileToUpload, fileToUpload.name);
const user = {
"name":"Vikram",
"age":35
}
const userTxt = JSON.stringify(user);
formData.append('userData',userTxt);
this.http.post('https://localhost:5001/api/upload/UploadFileWithJson', formData, {reportProgress: true, observe: 'events'})
.subscribe({
next: (event) => {
if (event.type === HttpEventType.UploadProgress)
this.progress = Math.round(100 * event.loaded / event.total);
else if (event.type === HttpEventType.Response) {
this.message = 'Upload success.';
this.onUploadFinished.emit(event.body);
}
},
error: (err: HttpErrorResponse) => console.log(err)
});
}
And .NET Core API
[HttpPost("UploadFileWithJson"), DisableRequestSizeLimit]
public IActionResult UploadFileWithJson([FromForm]UserWithFile model)
{
try
{
if (model == null)
throw new Exception($"{nameof(model)} is null");
if (model.File == null)
throw new Exception("File is null");
var folderName = Path.Combine("Resources", "Images");
var pathToSave = Path.Combine(Directory.GetCurrentDirectory(), folderName);
if (model.File.Length > 0)
{
var fileName = ContentDispositionHeaderValue.Parse(model.File.ContentDisposition).FileName.Trim('"');
var fullPath = Path.Combine(pathToSave, fileName);
var dbPath = Path.Combine(folderName, fileName);
using (var stream = new FileStream(fullPath, FileMode.Create))
{
model.File.CopyTo(stream);
}
return Ok(new { dbPath });
}
else
{
return BadRequest();
}
}
catch (Exception ex)
{
return StatusCode(500, $"Internal server error: {ex}");
}
}
as well as model class.
public class UserWithFile
{
public string UserData { get; set; }
public IFormFile File { get; set; }
}
Now notice, in Angular app, the FormData attribute file name first letter f is small-cased. However, in .NET Core same is File (or upper-cased). Same is case for userData.
I simply changed file and userData in Angular app to File and UserData respectively.
Voila! .