WebAPI core IFormFile always showing null - c#

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)

Related

Send multipart and json in same action

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

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

Custom Tag helper debugging

I'm trying to test a new tag helper that I'm trying to create. I've stumbled into a shortcoming of the new .net core validation and cannot change the class post validation. So if I wanted to give my errors a red background, the span is always there and won't change. So, I've decided to make my own tag helper. the problem is I can't seem to get it to work or trigger. I can't even get it to hit a break point. Here is what I have for a tag helper so far.
namespace MusicianProject.TagHelpers
{
// You may need to install the Microsoft.AspNetCore.Razor.Runtime package into your project
[HtmlTargetElement("invalid-class", Attributes = "validation-class")]
public class ValidateClassTagHelper : TagHelper
{
public ValidateClassTagHelper(IHtmlGenerator generator)
{
}
public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
return base.ProcessAsync(context, output);
}
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.Attributes.Add("class", "test");
var attr = context.AllAttributes;
}
}
}
and here is the usage in my register view.
<div class="container">
<form asp-controller="Account" asp-action="Register" method="post">
<div class="col-md-4 col-md-offset-4">
<div class="form-group">
<label asp-for="FirstName"></label>
<input class="form-control" type="text" asp-for="FirstName" />
<span asp-validation-for="FirstName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="LastName"></label>
<input class="form-control" asp-for="LastName" />
<span asp-validation-for="LastName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Email"></label>
<input class="form-control" type="text" asp-for="Email" />
<span validation-class="alert alert-danger" invalid-class="test" asp-validation-for="Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Password"></label>
<input asp-for="Password" type="password" id="password" class="form-control" />
<span asp-validation-for="Password" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ConfirmPassword"></label>
<input asp-for="ConfirmPassword" type="password" id="confirm-password" class="form-control" />
<span asp-validation-for="ConfirmPassword" class="text-danger"></span>
</div>
<div class="btn-group text-center">
<button class="btn btn-default">Sign up!</button>
<button class="btn btn-danger">Cancel</button>
</div>
</div>
</form>
</div>
#section Scripts {
#{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}
And yes I have register in my _ViewImports.cshtml file, the projects assembly name.
#using MusicianProject
#using MusicianProject.Models
#addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers"
#addTagHelper "*, MusicianProject"
Now I'm not sure if placement of certain files matters, but my _ViewImports.cshtml file is located within my views folder root (src/Views/_ViewImports.cshtml), and my tag helpers have their own folder in the root (src/TagHelpers/*).
What did I miss and how can I correct it?
You have 2 issues
1- The HtmlTargetElement is the name of the tag where this tag helper can be used, ex: span, div, table ...
2- You didn't use the tag helper in your code, accordingly, it will never get fired. it is not applied by default to all the tags specified in the HtmlTargetElement, you should call it by adding the validate-class attribute to the span tag.
To know more about tag helpers, please check this link https://blogs.msdn.microsoft.com/msgulfcommunity/2015/06/17/developing-custom-tag-helpers-in-asp-net-5/
If I understood correctly your problem is that your breakpoint doesn't get hit. That problem lies here:
[HtmlTargetElement("invalid-class", Attributes = "validation-class")]
The first parameter of HTMLTargetElement is the tag you are targeting, so in your case that would be "span". You entered a class name there.
This way at least your breakpoints will get hit and from there I'm sure you'll figure out the rest.

I am having issues sending data from a form post to my controller

I have an app that should accept 4 parameters from a form post which I am doing in my Index.cshtml class. I have my controller set to return a a csv file once it queries a database. I am using a form post and not sure what I am missing, because it is not sending any data to my controller in order to process the query and download the file.
This is my HTML
<div id="engagementForm" class="col-lg-offset-4">
#using (Html.BeginForm ("GetClientList", "Home", FormMethod.Post) )
{
<div class="form-horizontal" role="form">
<div class="form-group" id="marketSelection">
<label class="control-label col-sm-2" name ="marketGroup" id="marketGroup">Engagement Market:</label>
<div class="col-lg-10">
<input id="mrkGroup">
</div>
</div>
<div class="form-group" id="officeSelection">
<label class="control-label col-sm-2" name ="engagementOffice" id="engagementOffice">Engagement Office:</label>
<div class="col-sm-10">
<input id="engOffice">
</div>
</div>
<div class="form-group" id="partnerSelection">
<label class="control-label col-sm-2" Name ="engagementpartner" id="engagementpartner">Engagement Partner:</label>
<div class="col-sm-10">
<input id="engPartner">
</div>
</div>
<div class="form-group" id="statusSelection">
<label class="control-label col-sm-2" Name ="engagementStatus" id="engagementStatus">Engagement Status:</label>
<div class="col-sm-10">
<input id="engStatus">
</div>
</div>
<button class="k-button" type="submit" id="searchButton">Create Speardsheet</button>
</div>
}
This is my Controller
public ActionResult GetClientList(int? marketGroup, int? engagementOffice, int? engagementpartner, int? engagementStatus)
{
List<Engagement> QueryResult = PMService.GetRequestedEngagments(marketGroup, engagementOffice, engagementpartner, engagementStatus);
var writetofile = PMService.BuildCsvString(QueryResult);
var bytefile = Encoding.UTF8.GetBytes(writetofile);
Response.Clear();
Response.Buffer = true;
Response.AddHeader("content-disposition", "attachment;filename=SqlExport.csv");
Response.Charset = "";
Response.ContentType = "application/text";
Response.Output.Write(writetofile);
Response.Flush();
Response.End();
return View();
}
None of your inputs have name attributes:
<input id="mrkGroup">
So the browser has no way of identifying them in the key/value pairs of POST values. Simply add some names:
<input id="mrkGroup" name="marketGroup">
Side note: Returning a FileResult object would be vastly preferred over directly writing to the Response output in MVC. You'll find it a lot easier to debug and test. Writing to the output and returning a view seems... error-prone.

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.

Categories