Send multipart and json in same action - c#

I have Controller and Action for editing Person table.
I'm filling form with information and if user changes anything I enabling Save button to call action.
Person contains information and profile picture.
Problem is, Action is called only when I browse profile picture and have form sent as multipart/form-data. But if I calling it without sending file, just form I getting error 500.
If I want to send it as application/json and bind it to a model I must use [FromBody] annotation in Action for PersonModel parameter.
Now I sending multipart/form-data - and it only binds If I upload new picture, if I change only input fields - I'm getting error 500.
[Route("EditPerson")]
[HttpPost]
public IActionResult EditPerson(PersonDto Person) {
//Do something with person model
return Ok();
}
And I'm using jQuery-Form-Plugin:
$('#personEditForm').ajaxSubmit({
url: 'PersonSettings/EditPerson',
type: 'post',
contentType: 'multipart/form-data',
success: successEditPerson,
resetForm: true,
beforeSubmit: beforeEditPerson
});
Form:
<form id="personEditForm" >
<h6><b>General</b></h6>
<hr/>
<fieldset>
<div class="row">
<div class="col-md-4">
<div class="form-group">
<label for="Name">
Person Name :
<span class="danger">*</span>
</label>
<input autocomplete="off" type="text" class="form-control required" id="NameEdit" name="Name">
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label for="Surename">
Person Surename :
<span class="danger">*</span>
</label>
<input autocomplete="off" type="text" class="form-control required" id="SurenameEdit" name="Surename">
</div>
</div
<div class="col-md-4">
<div class="form-group">
<label for="Age">
Person Age :
<span class="danger">*</span>
</label>
<input autocomplete="off" type="text" class="form-control required" id="AgeEdit" name="Age">
</div>
</div
<div class="col-md-4">
<div class="form-group">
<label for="PersonPic">
Profile pic (Must be in size ..x..) :
<span class="danger">*</span>
</label>
<input type="file" class="form-control" id="PersonPic" name="PersonPic" accept="image/*" onchange="loadImageEdit(event)">
</div>
</div>
<div class="col-md-4 ">
<div class="form-group">
<label for="Name">
Profile picture preview:
</label>
<img id="personImagePreviewEdit" alt="Upload image to preview" style="display: block"src="#"/>
</div>
</div>
</div>
</fieldset></form>
I'm using beforeEditPerson function to append Id for Person.

You cannot send both multipart/form-data and application/json encoded requests to the same action. The modelbinder needs to know in advance how to handle the request body, requiring the specification of either [FromForm] (the default) or [FromBody]. If you need to handle both, then you need two separate actions, though you can factor out the actual logic to stay DRY. For example:
private IActionResult EditPersonCore(PersonDto person)
{
//Do something with person model
return Ok();
}
[HttpPost("EditPersonForm"]
public IActionResult EditPersonForm(PersonDto person) => EditPersonCore(person);
[HttpPost("EditPersonJson"]
public IActionResult EditPersonJson([FromBody]PersonDto person) => EditPersonCore(person);
Obviously, that means you'll have two different routes, but that's the best you can do.
That said, you don't need to post as multipart/form-data simply because you have a file. You can post a file via JSON; it simply needs to be sent as a Base64-encoded byte array. In JavaScript, you can do that via:
var reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function () {
// here `reader.result` will hold your file as base64
};
Server-side, you then simply need to bind to a property of type byte[]. The modelbinder in ASP.NET Core will transparently deserialize the Base64-encoded string into a byte[].

You can check request is contain Multipart content or not so accordingly, you can process the same. if contain the multipart data then do your logic here
[HttpPost]
public IActionResult Index([FromBody] personalInfo)
{
//check for multipart request
if (MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
{
// request for uploaded files
var file = Request.Form.Files[0];
}
}

Related

Razor Create Form for EF sub-object/navigation property collection?

Given an example object, say... Entry with a related object Comment where there is a foreign key from Comment to Entry called EntryID and is a collection (any number of comments for an entry), if a Razor page has a model of Entry and I want to have a form to create a new Comment, how would I do this? The scaffolded code is below and it would seem maybe I would say something like Comment.CommentText but that's not working because Comment is a collection...
//Code above this shows the Entry object as read-only followed by existing Comments,
//I'm trying to allow the user to create a new comment
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
...
<div class="form-group">
<label asp-for="CommentText" class="control-label"></label>
<input asp-for="CommentText" class="form-control" />
<span asp-validation-for="CommentText" class="text-danger"></span>
...
So what seems intuitive would be
<input asp-for="Comment.CommentText" class="form-control" />
but it's a collection so intellisense goes "Oh, this is a collection, here's some linq things and collection things."
How do?
If it's a collection you'll need a select tag like so:
<select class="form-control" asp-for="Comment" asp-items="#(new SelectList(Model.Comment))"></select>
or
<select class="form-control">
#foreach(var c in Model.Comment)
{
<option>#c</option>
}
</select>
Updated-Insert New Comment
You'll have to do a post on the form like so:
<form asp-action="ControllerMethodName" id="formId" asp-controller="ControllerName"
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="CommentText" class="control-label"></label>
<input asp-for="CommentText" class="form-control" id="commentText" />
<span asp-validation-for="CommentText" class="text-danger"></span>
<input type="submit" value="Submit" class="btn btn-primary"/>
</div>
</form
Controller:
[HttpPost]
public async Task<IActionResult> ControllerMethodName (CommentViewModel model)
{
await _svc.InsertComment(model); //_svc being your service layer=
return RedirectToPage("./Index")
//for return value
var result = await _svc.InsertComment(model);
return Json(result);
}
ViewModel:
public class CommentViewModel
{
public string CommentText {get; set;}
}
If you want to return a value to your html page, use the ajax or post method like so:
HTML:
$('#formId').submit(function (e) {
e.preventDefault();
var comment = $('#commentText').val();
$.ajax({
url: '#Url.Action("ControllerMethodName", "ControllerName")',
type: "POST",
data: {"CommentText": comment},
}).done(function (data, textStatus, jqXHR) {
//data will be your return value
}).fail(function (data, textStatus, errorThrown) {
//error handling
});
}
It seems the most straight-forward answer is to create a partial using the Comment object as a model, render a partial passing in a partially populated new Comment object as a model to the page.
Main/Parent Page:
#{ await Html.RenderPartialAsync("_CreateComment",new Comment() { Entry.ID = Model.Id}); }
Partial:
<form asp-action="Create">
//form fields
<input type="submit" value="Create" class="btn btn-primary" />

WebAPI core IFormFile always showing null

I have one front-end in angular 4, and one back-end in ASP.NET Core WebAPI, the functionality of them is, send data and an curriculum vitae of one people to one database which is SQL Server, but... always the IFormFile of method who catch the data and send file to the database is null.
I've already tried all type of solution that I found on the internet but none of them worked for me.
as response of the Post method i receive this exception
An unhandled exception occurred while processing the request.
NullReferenceException: Object reference not set to an instance of an object.
WebApplication.Controllers.CandidateController+<Post>d__4.MoveNext() in CandidateController.cs, line 60
Down here I pasted parts of the code of the project who do this.
github link for complete code:
front-end code
back-end code
HTML
<form class="col-md-6 offset-md-3" method="POST" enctype="multipart/form-data" #form="ngForm" (ngSubmit)="onSubmit(form)">
<div class="form-group row">
<label for="name" class="col-sm-2 col-form-label">Name</label>
<div class="col-sm-10">
<input type="text" class="form-control" placeholder="Nome completo" id="name" name="name" ngModel />
</div>
</div>
<div class="form-group row">
<label for="email" class="col-sm-2 col-form-label">Email</label>
<div class="col-sm-10">
<input type="email" class="form-control" id="email" placeholder="Email" name="email" ngModel />
<small id="emailHelp" class="form-text text-muted">
We'll never share your email with anyone else.
</small>
</div>
</div>
<div class="form-group row">
<label for="country" class="col-sm-2 col-form-label">Country</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="country" placeholder="Country" name="country" ngModel />
</div>
</div>
<div class="form-group row">
<label for="state" class="col-sm-2 col-form-label">State</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="state" placeholder="Estado" name="state" ngModel />
</div>
</div>
<div class="form-group row">
<label for="city" class="col-sm-2 col-form-label">City</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="city" placeholder="Cidade" name="city" ngModel />
</div>
</div>
<div class="form-group row">
<label for="file" class="col-sm-2 col-form-label">Curriculum</label>
<div class="col-sm-10">
<input type="file" id="file" name="file" ngModel />
</div>
</div>
<div class="container text-center">
<button type="submit" class="btn btn-outline-dark">Submit</button>
</div>
</form>
Angular 4
onSubmit(form: NgForm) {
const { file } = form.value;
delete form.value.file;
var data = new FormData();
data.append('Candidates', JSON.stringify(form.value));
data.append('file', file);
console.log(form.value);
console.log(file);
const headers = new Headers();
headers.append('Access-Control-Allow-Origin', '*');
const options = new RequestOptions({headers: headers});
this.http.post("http://localhost:54392/api/candidates", data, options)
.subscribe(
data => {
console.log("Foi");
},
error => {
console.log("Não foi");
});
}
C#
[HttpPost("candidates")]
public async Task<IActionResult> Post(IFormFile file)
{
var json = HttpContext.Request.Form["Candidates"];
var jsonTextReader = new JsonTextReader(new StringReader(json));
var candidate = new JsonSerializer().Deserialize<Candidate>(jsonTextReader);
if (!ModelState.IsValid)
return BadRequest();
using (var memoryStream = new MemoryStream())
{
await file.OpenReadStream().CopyToAsync(memoryStream);
candidate.CurriculumVitae = memoryStream.ToArray();
}
await dataBase.AddAsync(candidate);
dataBase.SaveChanges();
return Ok(candidate);
}
Upload images in Angular 4 without a plugin provides a walkthrough of the process you're attempting to achieve. It uses #ViewChild to grab a reference to the file input in the DOM and then uses that when building up the FormData. In your scenario, this involves a few changes, as follows:
Replace ngModel on the file input in your HTML with #file. This creates a template reference variable that can be accessed inside your component when using #ViewChild in the next step.
Add #ViewChild('file') fileInput; to the component, above your constructor. This links the #file template reference variable to your component code.
Remove the following code from your component:
const { file } = form.value;
delete form.value.file;
The file property will no longer exist at this point, so there's nothing to delete.
Replace data.append('file', file); with the following:
let fileBrowser = this.fileInput.nativeElement;
if (fileBrowser.files && fileBrowser.files[0]) {
data.append("file", fileBrowser.files[0]);
}
This last block of code grabs a handle on the file and appends it to your FormData.
You can name the variables and template reference variable whatever you want. The only thing that needs to be specific is the string value file used in data.append must match the variable name used for your C# IFormFile variable.
As an aside, you can also remove your custom Headers and RequestOptions as setting Access-Control-Allow-Origin as a request header is doing nothing here. This header should be set on the response, by the server.
I earlier had this issue, Got Fixed by removing. 'Content-Type', 'application/json; charset=utf-8'
And Also you need to ensure you have same parameter send and received both at client and server
Add the [FromBody] attribute. You can read more here: Binding formatted data from the request body
Change public async Task<IActionResult> Post(IFormFile file) to public async Task<IActionResult> Post([FromBody] IFormFile file)

How to post Form data and File in the same submit with Angular 4 in .NET Core

I am trying to add in the Suppliers table a new entry. This table also has another one linked to it in which are stored the contracts with each supplier.
The desired functionality is to submit in the same form both the data for the new Supplier and the contract, but I can't make it work.
I am able to add a new supplier and after the contract by uploading the file separately in another form, but not in the same submit action.
How can I do that?
My form looks like this:
<form class="form-material m-t-40" enctype="multipart/form-data">
<div class="form-group has-warning">
<h4 class="entity-info"><label for="denumire" class="label-has-danger"> Denumire furnizor </label></h4>
<input type="text" id="denumire" name="denumire" class="form-control" placeholder="Denumire" [(ngModel)]="furnizor.denumire" #denumire="ngModel">
</div>
<div class="form-group has-warning">
<h4 class="entity-info"><label for="cod" class="label-has-danger"> Cod intern furnizor </label></h4>
<input type="text" id="cod" name="cod" class="form-control" placeholder="Cod intern" [(ngModel)]="furnizor.cod" #cod="ngModel">
</div>
<div class="form-group has-warning">
<h4 class="entity-info"><label for="cod" class="label-has-success"> Cod de inregistrare fiscala furnizor </label></h4>
<input type="text" id="codIntern" name="codIntern" class="form-control" placeholder="Cod" [(ngModel)]="furnizor.codIntern" #codIntern="ngModel">
</div>
<div class="form-group has-warning">
<h4 class="entity-info"><label for="adresa" class="label-has-success"> Adresa </label></h4>
<input type="text" id="adresa" name="adresa" class="form-control" placeholder="Adresa" [(ngModel)]="furnizor.adresa">
</div>
<div class="form-group has-warning">
<h4 class="entity-info"><label for="persoanaContact" class="label-has-success"> Persoana Contact </label></h4>
<input type="text" id="persoanaContact" name="persoanaContact" class="form-control" placeholder="Persoana Contact" [(ngModel)]="furnizor.persoanaContact" #reprezentant="ngModel">
</div>
<div class="form-group has-warning">
<h4 class="entity-info"><label for="telefon" class="label-has-success"> Telefon </label></h4>
<input type="tel" id="telefon" name="telefon" class="form-control" placeholder="Telefon" [(ngModel)]="furnizor.telefon" #telefon="ngModel">
</div>
<div class="form-group has-warning">
<h4 class="entity-info"><label for="dataIncepereContract" class="label-has-success"> Data incepere contract </label></h4>
<input type="date" class="form-control m-b-40" placeholder="2017-06-04" id="dataIncepereContract" name="dataIncepereContract" [(ngModel)]="furnizor.dataIncepereContract" #dataIncepereContract="ngModel">
</div>
<div class="form-group has-warning">
<h4 class="entity-info"><label for="dataExpirareContract" class="label-has-success"> Data expirare contract </label></h4>
<input type="date" class="form-control m-b-40" placeholder="2017-06-04" id="dataExpirareContract" name="dataExpirareContract" [(ngModel)]="furnizor.dataExpirareContract" #dataExpirareContract="ngModel">
</div>
<input type="file" #fileInput>
<button type="button" class="btn btn-md btn-add animated flipInX flipInY lightSpeedIn slideInUp" (click)="submit()">
<i class="fa fa-floppy-o"></i> Save
</button>
</form>
I have tried a lot of options and combinations for the file and the other form fields, but none work.
My ts file reads the file input like this:
...
#ViewChild('fileInput') fileInput: ElementRef;
...
submit() {
var nativeElement: HTMLInputElement = this.fileInput.nativeElement;
if (nativeElement.files) this.file = nativeElement.files[0];
this.furnizorService.create(this.furnizor, this.file).subscribe(() => {});
}
My service:
create(furnizor: any, fileToUpload: any) {
let input = new FormData();
input.append("file", fileToUpload);
furnizor.file = input;
return this.http.post('/api/furnizor', furnizor).map(res => res.json());
}
And in my C# Controller I tried:
[HttpPost("/api/furnizor")]
public async Task<IActionResult> CreateFurnizor([FromBody]FurnizorResource furnizorResource)
{
...
}
Where I added a new property
public IFormFile file { get; set; }
in FurnizorResource, but if I do this, furnizorResource is not mapped and is null.
I also tried sending them separately (just to try):
[HttpPost("/api/furnizor")]
public async Task<IActionResult> CreateFurnizor([FromBody]FurnizorResource furnizorResource, IFormFile file)
{
...
}
(Which obviously doesn't work since in the Angular service I can't send 2 "body" elements). With this method the furnizorResource data is correct, but of course file is null...
How can I post Json data and a file at the same time in Angular 4.3.6 and ASP.NET Core 2?
Have you tried something like that? That's teh way I use it in my app and it works.
var formData = new FormData()
for (var key in model)
formData.append(key, furnizor[key])
formData.append('file', fileToUpload)
let headers = new Headers({ 'Content-Type': 'multipart/form-data' })
let options = new RequestOptions({ headers: headers })
return this.authHttp.post('/api/furnizor', formData, options).map(res => res.json())
I understand that your both, Angular and c# must be the property fornitureResources that it will be a array of FornitureResource
interface Forniture
{
cod:number;
...
fornitureResources :IFromFile[]
}
So, e.g.
let myforniture={
cod:1,
...
fornitureResources:[
{ file:"file1"},
{ file:"file2"}
]
}
If you send "myforniture" using this.http.post('/api/furnizor', myforniture) you must be get a "Forniture" in your API

How to pass string object data to mvc controller for posting?

This is my code
<form class="form-horizontal formtext col-xs-12 col-sm-10 col-lg-10 row">
<div class="form-group">
<div class="col-xs-12 col-sm-5 col-lg-3 paddnone">
<label class="control-label">First Name</label>
</div>
<div class="col-sm-5" ng-if="!newRecord">
<input type="text" class="form-control" ng-model="userDetails.firstname" />
</div>
<div class="col-sm-5" ng-if="newRecord">
<input type="text" class="form-control" ng-model="userDetails.firstName" />
</div>
</div>
<div class="form-group">
<div class="col-xs-12 col-sm-5 col-lg-3 paddnone">
<label class="control-label">Last Name</label>
</div>
<div class="col-sm-5" ng-if="!newRecord">
<input type="text" class="form-control" ng-model="userDetails.lastname" />
</div>
<div class="col-sm-5" ng-if="newRecord">
<input type="text" class="form-control" ng-model="userDetails.lastName" />
</div>
</div>
<div class="form-group">
<div class="col-xs-12 col-sm-5 col-lg-3 paddnone">
<label class="control-label">Email Id</label>
</div>
<div class="col-sm-5" ng-if="!newRecord">
<input type="text" class="form-control" ng-model="userDetails.emailid" />
</div>
<div class="col-sm-5" ng-if="newRecord">
<input type="text" class="form-control" ng-model="userDetails.emailId" />
</div>
</div>
<div class="col-xs-12 col-sm-9 col-lg-7 btnbg">
<button class="btn btn-primary " ng-click="create(userDetails)">
SAVE
</button>
<button class="btn btn-primary ">
Cancel
</button>
</div>
$scope.create = function (userDetails) {
var userData = angular.toJson(userDetails);
return dataFactory.post("/Usercontroller/Save", userData)
.then(function (result) {
alert("save successfully");
});
public class UserDataModel
{
public UserDataModel ();
public Dictionary<string, string> Data { get; set; }
public string FormName { get; set; }
}
public async Task<JsonNetResult> SaveUser(string applicationId, dynamic user)
{
Dictionary<string, string> dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(user);
UserDataModel userDataModel = new UserDataModel ();
//userDataModel .Data = dict;
userDataModel.FormName = "one";
userDataModel = await Post<UserDataModel>(new UserDataModel{ Data = dict });
return new JsonNetResult { Data = userDataModel };
}
In the above code when i click save button data pass to create function in js,then the entire data convert to string and pass to mvc controller , but the dynamic user got object , now how to deserialze and store to webapi.
So you have two options here, 1) You should use razor cshtml instead
of html, it will ease your life. Following link will help you.
http://blog.michaelckennedy.net/2012/01/20/building-asp-net-mvc-forms-with-razor/
If you want to use existing html code:
a) You have to make the name of all fields same as your class object variable, it will automatically parse it to object when server side c# code got the control after form submission.
b) Make the parameter of c# form submission handler function to FormCollection
Following links will help you,
ASP.NET MVC 3 Razor: Passing Data from View to Controller
Deserialization:
var json = JsonConvert.DeserializeObject<dynamic>(sampleJson);
var data = ((JObject)json.data).Children();
Deserialize JSON with dynamic objects
Have your HTML code like this, using Razor Syntax
#using (Html.BeginForm("Upload", "ModelClass")
{
#Html.ValidationSummary(true)
//HTML elements which are based on your model class members.
}
In your controller class method, it would send this form data to the Upload Method.
[HttpPost]
public ActionResult Upload(ModelClass model)
{
//Send model to the model object to Model class for processing
return RedirectToAction("Upload");
}
EDIT: This method I have used in ASP.NET MVC 5 on Visual Studio community edition 2013.

Upload multiple files in one form MVC4

I'm trying to upload multiple images on one form
#using (Html.BeginForm("Create", "AdminRestaurants", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<div class="form-group">
<label for="logoFile" class="col-sm-2 control-label">Logo:</label>
<div class="col-sm-6">
<input type="file" multiple="" name="logoFile" id="logoFile" />
</div>
</div>
<div class="form-group">
<label for="fohFile" class="col-sm-2 control-label">FOH Logo:</label>
<div class="col-sm-6">
<input type="file" multiple="" name="fohFile" id="fohFile" />
</div>
</div>
<div class="form-group">
<label for="bohFile" class="col-sm-2 control-label">BOH Logo:</label>
<div class="col-sm-6">
<input type="file" multiple="" name="bohFile" id="bohFile" />
</div>
</div>
<div class="form-group">
<label for="mgmFile" class="col-sm-2 control-label">MGM Logo:</label>
<div class="col-sm-6">
<input type="file" multiple="" name="mgmFile" id="mgmFile" />
</div>
</div>
I'm trying to process the form on the controller with this
public ActionResult Create(IEnumerable<HttpPostedFileBase> files, RestaurantModel collection)
{
if (ViewData.ModelState.IsValid)
{
}
}
Currently nothing shows up in the files signature on the controller. This seems to work great when only working with one file
public ActionResult Create(HttpPostedFileBase file, EventsModel collection)
Can someone point me in the direction to allow multiple files to be uploaded with one submission form?
Your problem is that you form creates a post request with information that the model binder can bind because the naming convention is not right.
you see, you have 4 file fields each with a different name, for the model binder to bind them correctly your controller action signature should look like this:
public ActionResult Create(HttpPostedFileBase mgmFile,
HttpPostedFileBase logoFile,
HttpPostedFileBase fohFile ,
HttpPostedFileBase bohFile)
Following MCV design pattern The best option would be to use a ViewModel that holds an IEnumerable<HttpPostedFileBase>
and you would create a custom editor template for an IEnumerable<HttpPostedFileBase>
so that you could use it like that:
Html.EditorFor(m=>Model.filesUploaded)
and your controller action would look like this:
public ActionResult Create(MyViewModel i_InputModel)
{
i_InputModel.filesUploade; //Use the files here to upload them.
}
Other options are:
Use the HTML5 multiple attribute on the file input field like this:
<label for="mgmFile" class="col-sm-2 control-label">Files:</label>
<div class="col-sm-6">
<input type="file" multiple="multiple" name="files" id="files" />
</div>
and a controller action like this:
public ActionResult Create(HttpPostedFileBase files)
or use multiple file fields but index them in their name:
<input type="file" multiple="multiple" name="files[0]" id="files_1" />
<input type="file" multiple="multiple" name="files[1]" id="files_2" />
<input type="file" multiple="multiple" name="files[2]" id="files_3" />
<input type="file" multiple="multiple" name="files[3]" id="files_4" />
and then you could use a controller action like this:
public ActionResult Create(IEnumerable<HttpPostedFileBase> files)
This would only work if your file inputs had indexed names like files[0], files[1], files[2],...
In order to understand how model binding to a list works in asp.net mvc, I recommend that you read this post: Model Binding to a List
You don't even have to use model binding to get the files. Use Request.Files in your Action to get them.
public ActionResult Create(EventsModel collection)
{
var files = Request.Files;
// rest of the code...
}
<td>
<input type="file" name="files" id="files" multiple="multiple" />
</td>
As Here, I demonstrate with simple example : http://www.infinetsoft.com/Post/How-to-create-multiple-fileUpload-using-asp-net-MVC-4/1229#.V0J-yfl97IU

Categories