Retrieving data appended to formData in C# - c#

In this scenario:
var xhr = new XMLHttpRequest();
var fd = new FormData();
fd.append("file", document.getElementById('fileUpload').files[0]);
fd.append("PhoneId", '1234');
xhr.open("POST", '/Main/Upload', true);
xhr.send(fd);
xhr.addEventListener('load', function (event) {
var test = event.target.response;
})
I have a file upload along with an integer attached. My controller is:
public ActionResult Upload(Model newModel)
{
NewFiles files = new NewFiles ();
try
{
HttpPostedFileBase file = Request.Files[0];
}
catch(Exception)
{}
}
public class newModel
{
public int FileID { get; set; }
public string ReturnAction { get; set; }
public HttpPostedFileBase fileUpload { get; set; }
public int PhoneId { get; set; }
}
The uploaded file gets recognized, but what do I need in C# to view the uploaded PhoneId?

Your syntax is a bit off in the controller and won't compile. Changing the controller to the following syntax should work. This shows how to access the POSTed file and the model properties.
[HttpPost]
public ActionResult Upload(newModel model, HttpPostedFileBase file)
{
// Check the file is valid.
if (file == null || file.ContentLength == 0 || string.IsNullOrEmpty(file.FileName))
ModelState.AddModelError("fileUpload", "Invalid file uploaded.");
// Access model properties as you wish, like this:
int phoneID = model.PhoneId;
return null;
}
A couple of extra things to improve your code:
Generally speaking, when defining a class, the name of it should be in the format of CapitalizeEachWord.
It would be a lot easier if you used jQuery for your AJAX request rather than the normal JavaScript way of doing it. By using the normal JavaScript way, you're having to manually assign form properties to a FormData object, wheras with jQuery you can simpily do $("#formID").serialize().

it will work
string phoneId = Request.Form.Get("PhoneId");

Related

Upload file in angular / web API

I created a function that add a new user with Angular/WebAPI , so in angular i send the object like this :
AddUser(person : person):Observable<person>
{
return this.http.post<person>(this.baseUrl,person);
}
and in WebAPI i got the data :
[HttpPost]
public async Task<ActionResult<Person>> PostPerson(Person person)
{
Now I want to add a picture of each user, and i don't know how it's gonna be the objet in angular, should i add a property imageProfile as File which i'm not sure if it's possible , or it should be a string of the uploaded file.
export class person {
idPerson:number=0;
fname : string ="" ;
lname : string ="";
password : string ="";
idCategory? :number;
nameCategory:string="";
imageProfile :
}
That's very good info from Codemaze, as always. The remark from Lavstsen is not correct, there's no need for a separate call.
For example, you could have a form linked to the structure of the person-type:
initializeForm() {
this.personForm = this.form.group({
fname: [''],
})
}
In your onSubmit-method, you seize the date from your form and put in a dto, in this example through a getPersonDto-method
private getPersonDto(): PersonDto {
let dto = new PersonDto();
dto.fname = this.imageForm.controls.fname.value;
return dto;
}
and next you can solve it like this:
const formData = new FormData();
formData.append('Imagefile', this.selectedFile, this.selectedFile.name);
formData.append('fname', personDto.fname); // needs to be strings => toString()
this.personApiClient.uploadImageFile(this.personId, formData)
.subscribe((res: any) => {
this.uploadOrDeleteResults = res;
this.showToastrMessageSuccess("PAGES.COMMON.TOASTR.TITLE.SUCCESSFULCREATE", "PAGES.COMMON.TOASTR.MESSAGE.SUCCESSFULCREATE");
},
(error) => {
this.crudHasErrors = true;
this.errorHttpErrorResponse = error;
this.errors = this.errorHttpErrorResponse.error;
},
you don't need the dto-step as such, but if you structurally always use dto's in your application, it would make more sense
Take care and good luck
I have another workaround but this will only work for very small files where you don't need upload percentage to show on UI.
I have my controller like this:
[HttpPost("/api/sign-up")]
public void SaveUser([FromForm] UserModel info)
{
_logger.LogDebug(null, "Hello", info); // Just to see contents of info object in debugger
}
and make your model like this:
public class UserModel
{
[FromForm(Name = "avatar")]
public IFormFile Avatar { get; set; }
[FromForm(Name = "email")]
public string Email { get; set; }
}

FormData key does not go through

I use external class for making object variable into FormData variable. All of the keys go through, all but One for File object.
Here is that external class: https://github.com/therealparmesh/object-to-formdata
This is how I create objects and make them into FormData
var _documents = [];
for (var i = 0; i < arrayOfFiles.length; i++) {
var document = {
File: arrayOfFiles[i].file.nativeFile,
DocumentId: arrayOfFiles[i].documentId,
DocumentType: arrayOfFiles[i].documentName
};
_documents.push(document);
}
var uploadedInformation = {
LoanID: 1452465,
documents: _documents
};
var options = {
indices: true,
nulls: true
};
var a = objectToFormData(uploadedInformation, options);
for (var pair of a.entries()) {
console.log(pair[0] + ', ' + pair[1]);
}
jQuery.ajaxSettings.traditional = true;
$.ajax({
async: false,
cache: false,
contentType: false,
processData: false,
type: 'POST',
url: '#Url.Action("UploadFile", "Home")',
data: a
});
Code for controller:
[HttpPost]
[ActionName("UploadFile")]
public ActionResult UploadFile(UploadedInformation uploadedInformation)
{
_ = Request.Form;
return View();
}
UploadedFile class:
public class UploadedInformation
{
public long LoanID { get; set; }
public IEnumerable<Document> Documents { get; set; }
}
Document class:
public class Document
{
public HttpPostedFileBase File { get; set;}
public string DocumentId { get; set;}
public string DocumentType { get; set; }
}
All of the items bind perfectly, except for File.
In browser debugger keys and values are:
LoanID, 1452465
documents[0][File], [object File]
documents[0][DocumentId], 1
documents[0][DocumentType], Passport
_=Request.Form also displays only 3 keys without documents[0][File]
Update:
I changed controller to
public ActionResult UploadFile(IEnumerable<HttpPostedFileBase> file, IEnumerable<string> documentType, IEnumerable<string>documentId, long loanId){...}
and _=Request.Form still shows nothing with file, however file list is populated
Another update:
Apparently, this time file items shows only in _=Request.File
Due to the way that the controller is handling the file upload parts of the request, I suspect you may need to make some adjustments to your process and allow for the fact that the files are being separated from the main object.
I've included some adjustments to your code below (note, this is untested so you may need to experiment a little), assuming that the files come through in the same order as you Documents, then just run a match up process before running your own process.
Code for controller:
[HttpPost]
[ActionName("UploadFile")]
public ActionResult UploadFile(List<HttpPostedFileBase> myFiles, UploadedInformation uploadedInformation)
{
for (var i = 0; i <uploadedInformation.Documents.Length; i++)
{
uploadedInformation.Documents[i].File = myFiles[i];
}
// Do stuff
return View();
}
In the event that the order of the files cannot be assumed, you can add the filename to the data to aid with matching on the server side
Javascript
var _documents = [];
for (var i = 0; i < arrayOfFiles.length; i++) {
var document = {
File: arrayOfFiles[i].file.nativeFile,
FileName: arrayOfFiles[i].file.name,
DocumentId: arrayOfFiles[i].documentId,
DocumentType: arrayOfFiles[i].documentName
};
_documents.push(document);
}

How to create a FileUpload view / viewmodel that isn't an entity in the database

I am working on an auction application and I am creating a method so that the admins can submit an excel spreadsheet that will create a new auction and store it in the database. So first I made a class (model) Uploadfile like this:
[NotMapped]
public class UploadFile
{
[Required]
public HttpPostedFileBase ExcelFile { get; set; }
}
I used NotMapped because I am trying to understand how to create and use models that aren't stored in my database and this is where my issue and misunderstanding lies.
I created a controller, which I did manually since UploadFile is not an entity with a key as such:
public class FileUploadsController : Controller
{
private AuctionEntities db = new AuctionEntities();
// GET: FileUploads
public ActionResult Index()
{
UploadFile UploadFile = new UploadFile();
return View(UploadFile);
}
[HttpPost]
public ActionResult Index(UploadFile UploadFile)
{
if (ModelState.IsValid)
{
if (UploadFile.ExcelFile.ContentLength > 0)
{
if (UploadFile.ExcelFile.FileName.EndsWith(".xlsx") || UploadFile.ExcelFile.FileName.EndsWith(".xls"))
{
XLWorkbook wb;
// in case if the file is corrupt
try
{
wb = new XLWorkbook(UploadFile.ExcelFile.InputStream);
}
catch (Exception ex)
{
ModelState.AddModelError(String.Empty, $"Check your file. {ex.Message}");
return View();
}
IXLWorksheet ws = null;
try // in case the sheet you are looking for is not found
{
ws = wb.Worksheet("sheet1");
}
catch
{
ModelState.AddModelError(String.Empty, "Sheet not found");
return View();
}
var firstRowUsed = ws.FirstRowUsed();
var auctionRow = firstRowUsed.RowUsed().RowBelow();
// create auction
string auctionName = auctionRow.Cell(1).Value.ToString();
DateTimeOffset startDate = DateTimeOffset.Parse(auctionRow.Cell(2).Value.ToString());
DateTimeOffset endDate = DateTimeOffset.Parse(auctionRow.Cell(3).Value.ToString());
string folderName = auctionRow.Cell(4).Value.ToString();
Models.Auction auction = new Models.Auction(auctionName, startDate, endDate, folderName);
db.Auctions.Add(auction);
// find the next table
var nextRow = auctionRow.RowBelow();
while (nextRow.IsEmpty())
{
nextRow = nextRow.RowBelow();
}
const int catNameCol = 1;
var catRow = nextRow.RowUsed().RowBelow();
// get categories from ws table and add to the auction
while (!catRow.Cell(catNameCol).IsEmpty())
{
string catName = catRow.Cell(1).Value.ToString();
int seqNo = Convert.ToInt32(catRow.Cell(2).Value.ToString());
string fileName = catRow.Cell(3).Value.ToString();
Cat cat = new Cat(auction.AuctionId, catName, seqNo, fileName);
auction.Cats.Add(cat);
catRow = catRow.RowBelow();
}
var findNextRow = catRow.RowBelow();
while (findNextRow.IsEmpty())
{
findNextRow = findNextRow.RowBelow();
}
const int itemNameCol = 1;
var itemRow = findNextRow.RowUsed().RowBelow();
while(!itemRow.Cell(itemNameCol).IsEmpty())
{
string itemName = itemRow.Cell(1).Value.ToString();
string itemDesc = itemRow.Cell(2).Value.ToString();
string catName = itemRow.Cell(3).Value.ToString();
string modelNo = itemRow.Cell(4).Value.ToString();
decimal retailValue = Convert.ToDecimal(itemRow.Cell(5).Value.ToString());
string fileName = itemRow.Cell(6).Value.ToString();
decimal initialBid = Convert.ToDecimal(itemRow.Cell(7).Value.ToString());
decimal increment = Convert.ToDecimal(itemRow.Cell(8).Value.ToString());
Cat itemCat = null;
foreach(var cat in auction.Cats)
{
if(catName == cat.CatName)
{
itemCat = cat;
}
}
Item item = new Item(itemName, itemDesc, modelNo, retailValue, fileName, startDate, endDate, initialBid, increment, null, null, null, itemCat);
itemCat.Items.Add(item);
itemRow = itemRow.RowBelow();
}
}
else
{
ModelState.AddModelError(String.Empty, "Only .xlsx and .xls files are allowed");
return View();
}
}
else
{
ModelState.AddModelError(String.Empty, "Not a valid file");
return View();
}
}
db.SaveChanges();
return View();
}
Next I thought I would try to create a view again so that I can display where the user uploads the file and see if my method works and this is where I have run into my lack of knowledge in asp.net.
So I tried to create a ViewModel as I have seen since the model I created before was a data model, so that I could use this viewmodel to display the upload on my view page. My ViewModel is simple and is:
public class FileUploadViewModel
{
public HttpPostedFileBase ExcelFile { get; set; }
}
Now, I wanted to create a view page for this viewmodel and it is still treating this model has an entity and giving me an error that it does not have a key etc. I need a viewpage that can access a model with the Excel file in it and I can't seem to figure out how to accomplish this. I have read up on viewmodels and I know how crucial they are in MVC, however I just can't seem to grasp on how to use them. Can someone please help me understand how to use one here?
Basically, I want to use this view page with my model or viewmodel:
My educated guess is that you are getting stuck in the "Add View" window.
You are probably selecting a template that requires a model (e.g. Create), selecting your FileUploadViewModel class as the model and also your context.
What this does is it causes the Visual Studio "wizard" to try to map the model internally to your context, which results on the error you see.
Instead, select Empty (without model) as the template which will gray out the Model and Data Context fields. Your view will then be created without errors.
You can then tell the view to expect your model by adding this at the top:
#model FileUploadViewModel
Make sure you fully qualify FileUploadViewModel (e.g. include the namespace in front).
Your methods should now use the model you specified at the top of the view:
public ActionResult Index()
{
FileUploadViewModel UploadFile = new FileUploadViewModel();
return View(UploadFile);
}
[HttpPost]
public ActionResult Index(FileUploadViewModel UploadFile)
{
}
You do not need the [NotMapped] attribute anywhere here.

Json data coming back null

Good evening, guys! I've got an odd one.
Long story short, I'm sending a post from a java script client-side to get an integer back from my C# Controller, but the response.data is coming back null. The twist is that both the C# method and the javascript/jquery function are literally copied and pasted from another project where both of them work. The project they're taken from is a VS2010 project, and they're pasted into a VS2012 project. I'm not sure if this is the issue, but it could be related. The integer is fetched correctly in the C# and none of the information is missing. Even more mysteriously, the success message is coming back correctly to the response object client-side. However the response.data object is null, and throws an exception.
Any and all help is very much appreciated. Thanks!
This is the method in the C#:
[HttpPost]
public JsonResult GetMaxFileSize()
{
int MaxFileSize = 0;
// Get max file size.
string MaxPatientFileSizeInMegsString = System.Web.Configuration.WebConfigurationManager.AppSettings["MaxFacilityLogoFileSizeInMegs"];
MaxFileSize = int.Parse(MaxPatientFileSizeInMegsString);
return Json(new AjaxResponse(true, "Success.", new { maxFileSize = MaxFileSize }));
}
And this is the javascript/jquery function:
function getMaxFileSize() {
$.post(settings.actions.getMaxFileSize, function (response) {
var maxFileSize = 0;
// Assign the correct size to the hidden field.
if (response.success) {
maxFileSize = response.data.maxFileSize;
$(settings.selectors.maxFileSizeHiddenInput).val(maxFileSize);
}
// Assign 0 to max file size: user cannot upload files.
else {
$(settings.selectors.maxFileSizeHiddenInput).val(maxFileSize);
}
});
}
Works fine here!
TestController.cs
public class AjaxResponse
{
public AjaxResponse(bool success, object data)
{
this.success = success;
this.data = data;
}
public bool success { get; set; }
public object data { get; set; }
}
[HttpPost]
public ActionResult Ajax()
{
return Json(new AjaxResponse(true, new { num = 5 }));
}
Index.cshtml
$.post('#Url.Action("Ajax", "Test")', function (response) {
var num = 0;
debugger;
// Assign the correct size to the hidden field.
if (response.success) {
num = response.data.num;
$('h2').html(num);
}
// Assign 0 to max file size: user cannot upload files.
else {
$('h2').html(num);
}
});
JSON response in FireBug:
{"success":true,"data":{"num":5}}

Selectively Whitelisting Model Fields to Bind

(I realize this question is very similar to How to whitelist/blacklist child object fields in the ModelBinder/UpdateModel method? but my situation is slightly different and there may be a better solution available now that wasn't then.)
Our company sells web-based software that is extremely configurable by the end-user. The nature of this flexibility means that we must do a number of things at run time that would normally be done at compile time.
There are some rather complex rules regarding who has read or read/write access to most everything.
For instance, take this model that we would like to create:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace j6.Business.Site.Models
{
public class ModelBindModel
{
[Required]
[Whitelist(ReadAccess = true, WriteAccess = true)]
public string FirstName { get; set; }
[Whitelist(ReadAccess = true, WriteAccess = true)]
public string MiddleName { get; set; }
[Required]
[Whitelist(ReadAccess = true, WriteAccess = true)]
public string LastName { get; set; }
[Required]
[Whitelist(ReadAccess = User.CanReadSalary, WriteAccess = User.CanWriteSalary)]
public string Salary { get; set; }
[Required]
[Whitelist(ReadAccess = User.CanReadSsn, WriteAccess = User.CanWriteSsn)]
public string Ssn { get; set; }
[Required]
public string SirNotAppearingOnThisPage { get; set; }
}
}
In the controller, it is not difficult to "unbind" things manually.
var resetValue = null;
modelState.Remove(field);
pi = model.GetType().GetProperty(field);
if (pi == null)
{
throw new Exception("An exception occured in ModelHelper.RemoveUnwanted. Field " +
field +
" does not exist in the model " + model.GetType().FullName);
}
// Set the default value.
pi.SetValue(model, resetValue, null);
Using HTML helpers, I can easily access the model metadata and suppress rendering of any fields the user does not have access to.
The kicker: I can't figure out how to access the model metadata anywhere in the CONTROLLER itself to prevent over-posting.
Note that using [Bind(Include...)] is not a functional solution, at least not without additional support. The properties to Include are run-time (not compile time) dependent, and excluding the property does not remove it from the validation.
ViewData.Model is null
ViewData.ModelMetaData is null
[AllowAnonymous]
[HttpPost]
// [Bind(Exclude = "Dummy1" + ",Dummy2")]
public ViewResult Index(ModelBindModel dto)
{
zzz.ModelHelper.RemoveUnwanted(ModelState, dto, new string[] {"Salary", "Ssn"});
ViewBag.Method = "Post";
if (!ModelState.IsValid)
{
return View(dto);
}
return View(dto);
}
Any suggestions on how to access the Model MetaData from the controller? Or a better way to whitelist properties at run time?
Update:
I borrowed a page from this rather excellent resource:
http://www.dotnetcurry.com/ShowArticle.aspx?ID=687
With a model that looks like this:
[Required]
[WhiteList(ReadAccessRule = "Nope", WriteAccessRule = "Nope")]
public string FirstName { get; set; }
[Required]
[WhiteList(ReadAccessRule = "Database.CanRead.Key", WriteAccessRule = "Database.CanWrite.Key")]
public string LastName { get; set; }
The class:
public class WhiteList : Attribute
{
public string ReadAccessRule { get; set; }
public string WriteAccessRule { get; set; }
public Dictionary<string, object> OptionalAttributes()
{
var options = new Dictionary<string, object>();
var canRead = false;
if (ReadAccessRule != "")
{
options.Add("readaccessrule", ReadAccessRule);
}
if (WriteAccessRule != "")
{
options.Add("writeaccessrule", WriteAccessRule);
}
if (ReadAccessRule == "Database.CanRead.Key")
{
canRead = true;
}
options.Add("canread", canRead);
options.Add("always", "be there");
return options;
}
}
And adding these lines to the MetadataProvider class mentioned in the link:
var whiteListValues = attributes.OfType<WhiteList>().FirstOrDefault();
if (whiteListValues != null)
{
metadata.AdditionalValues.Add("WhiteList", whiteListValues.OptionalAttributes());
}
Finally, the heart of the system:
public static void DemandFieldAuthorization<T>(ModelStateDictionary modelState, T model)
{
var metaData = ModelMetadataProviders
.Current
.GetMetadataForType(null, model.GetType());
var props = model.GetType().GetProperties();
foreach (var p in metaData.Properties)
{
if (p.AdditionalValues.ContainsKey("WhiteList"))
{
var whiteListDictionary = (Dictionary<string, object>) p.AdditionalValues["WhiteList"];
var key = "canread";
if (whiteListDictionary.ContainsKey(key))
{
var value = (bool) whiteListDictionary[key];
if (!value)
{
RemoveUnwanted(modelState, model, p.PropertyName);
}
}
}
}
}
To recap my interpretation of your question:
Field access is dynamic; some users may be able to write to a field and some may not.
You have a solution to control this in the view.
You want to prevent a malicious form submission from sending restricted properties, which the model binder will then assign to your model.
Perhaps something like this?
// control general access to the method with attributes
[HttpPost, SomeOtherAttributes]
public ViewResult Edit( Foo model ){
// presumably, you must know the user to apply permissions?
DemandFieldAuthorization( model, user );
// if the prior call didn't throw, continue as usual
if (!ModelState.IsValid){
return View(dto);
}
return View(dto);
}
private void DemandFieldAuthorization<T>( T model, User user ){
// read the model's property metadata
// check the user's permissions
// check the actual POST message
// throw if unauthorized
}
I wrote an extension method a year or so ago that has stood me in good stead a couple of times since. I hope this is of some help, despite not being perhaps the full solution for you. It essentially only allows validation on the fields that have been present on the form sent to the controller:
internal static void ValidateOnlyIncomingFields(this ModelStateDictionary modelStateDictionary, FormCollection formCollection)
{
IEnumerable<string> keysWithNoIncomingValue = null;
IValueProvider valueProvider = null;
try
{
// Transform into a value provider for linq/iteration.
valueProvider = formCollection.ToValueProvider();
// Get all validation keys from the model that haven't just been on screen...
keysWithNoIncomingValue = modelStateDictionary.Keys.Where(keyString => !valueProvider.ContainsPrefix(keyString));
// ...and clear them.
foreach (string errorKey in keysWithNoIncomingValue)
modelStateDictionary[errorKey].Errors.Clear();
}
catch (Exception exception)
{
Functions.LogError(exception);
}
}
Usage:
ModelState.ValidateOnlyIncomingFields(formCollection);
And you'll need a FormCollection parameter on your ActionResult declaration, of course:
public ActionResult MyAction (FormCollection formCollection) {

Categories