HttpPostedFileBase null when inside ViewModel - c#

I searched the internet for possible problems that might be causing this and found nothing ...
When I put the IEnumerable<HttpPostedFileBase> FilesUploadedEvent as action parameter alongside ViewModel everything seems to be working fine.
[HttpPost]
public ActionResult Create(EventViewModel model, IEnumerable<HttpPostedFileBase> FilesUploadedEvent)
{
if (ModelState.IsValid)
{
...
Problem is when I try to put it inside object in my ViewModel
#model ViewModels.Event.EventViewModel
#using (Html.BeginForm("Create", "Event", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.ValidationSummary(true, null, new { #class = "validation-msg" })
#Html.EditorFor(m => m.BasicInfoSection)
...
<input type="submit" value="Add" />
}
Controller after removing 2nd parameter:
[HttpPost]
public ActionResult Create(EventViewModel model)
{
if (ModelState.IsValid)
{
...
ViewModel:
public class EventViewModel
{
public BasicInfoSection BasicInfoSection { get; set; }
...
And we put the parameter inside ViewModels object:
public class BasicInfoSection : IValidatableObject
{
public string Remarks { get; set; }
public IEnumerable<HttpPostedFileBase> FilesUploadedEvent { get; set; }
...
and here is editor template for BasicInfoSection :
#model ViewModels.Event.Parts.BasicInfoSection
<input id="FilesUploadedEvent" name="FilesUploadedEvent" type="file" data-role="upload" multiple="multiple" autocomplete="off">
#(Html.TextAreaFor(m => m.Remarks, new { #class = "form-control", style = "height:200px;" }))
...
Also if the form wont validate on some other fields is it possible to return Files in the postBack? I read it is not because of security reasons. Is Ajax validation the only method than?

The name attribute of the file input is not correct. Because FilesUploadedEvent is a property of BasicInfoSection, then in order to be bound on post back, the input would need to be
<input name="BasicInfoSection.FilesUploadedEvent" type="file" .. />
Note also how the textareahas the name attribute name="BasicInfoSection.Remarks"

Related

C# razorview DropDownListFor 'Value cannot be null'

I am new to ASP.NET MVC and I'm working on my first project just for fun.
I get this ArgumentNullException and I cannot figure out what's wrong.
This is my model:
public class SpeciesLabel
{
[Key]
[Required]
public string Name { get; set; }
[Required]
public CustomGroup CustomGroup { get; set; }
[Required]
public Family Family { get; set; }
[Required]
public Genus Genus { get; set; }
[Required]
public Species Species { get; set; }
}
public class SpeciesLabelDbContext : DbContext
{
public SpeciesLabelDbContext()
: base("DefaultConnection")
{
}
public DbSet<SpeciesLabel> SpeciesLabel { get; set; }
}
This is the controller:
public ActionResult Create()
{
List<SelectListItem> customGroups = new List<SelectListItem>();
IQueryable<string> customGroupsQuery = from g in customGroupsDb.CustomGroup
select g.Name;
foreach (var element in customGroupsQuery)
{
customGroups.Add(new SelectListItem()
{
Value = element,
Text = element
});
}
ViewBag.CustomGroup = customGroups;
This is the controller POST request:
public ActionResult Create([Bind(Include = "CustomGroup,Family,Genus,Species")] SpeciesLabel speciesLabel)
{
if (ModelState.IsValid)
{
db.SpeciesLabel.Add(speciesLabel);
db.SaveChanges();
return RedirectToAction("Create");
}
return View();
}
And this is the view:
<pre>
#model PlantM.Models.PlantModels.SpeciesLabel
#{
ViewBag.Title = "Create";
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Species label</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.CustomGroup, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.CustomGroup, new SelectList(ViewBag.CustomGroupList, "Value", "Text"), "Please select...", new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.CustomGroup, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
</pre>
I have inputs for all properties in the view but I cut them as they are similar to this one and the exception would be the same. Only property Name is not returned from the view as it will be designed in the controller (concatenation of the other properties).
This is the exception I get when I submit the form:
ArgumentNullException
Edit:
After adding the ViewBag initialization in the POST Create method the problem with the ArgumentNullException is resolved but I still receive Null value arguments and the object cannot be created due to this and the Create view is recalled again and again!? Can anyone advise why these #Html.DropDownListFor do not post any value to the controller?
From the comment, it sound like you see the view on first visit, but a null exception happen after you post.
If above assumption is correct, then I think your problem is because when you post back, your model did not pass the validation (for example, maybe a required input field did not post back value), which means ModelState.IsValid is false, so return View() was called
Here is the problem, you are not setting the ViewBag.CustomGroup = customGroups; in before return, hence ViewBag.CustomGroup is null, that is why you are seeing the exception.
init the ViewBag like how you did it on get then you should be able to see the page.

Bind multiple values to a single checkbox and post it to controller

Model.cs
A campaign can have multiple images, that's why IEnumerable<int> ImageIdList.
public class Campaign
{
public int Id { get; set; }
public int CreatedBy { get; set; }
public int UpdatedBy { get; set; }
public IEnumerable<int> ImageIdList { get; set; }
}
View.cshtml
I want to download all the images related to a campaign, based on the ImageIdList, that's why I need to post all these ImageIds when a particular Campaign is checked and download button is clicked.
#model Campaign
#{
Layout = "....";
var assets = Model.AssetsInCampaign.ToList();
}
#using (Html.BeginForm("action-method", "controller", FormMethod.Post))
{
<div class="btnSubmit">
<input type="submit" value="Download Asset(s)" />
</div>
#foreach(var i in assets)
{
<div class="s_checkcol">
<input type="checkbox" name="ids" />
#foreach (var imageId in i.Where(c => c.AssetId == doc.FileDataId).SelectMany(c => c.ImageIdList))
{
<input type="hidden" name="ids" value=#(imageId)>
}
</div>
}
}
Controller.cs
public ActionResult DownloadFiles(IEnumerable<int> ids)
{
// code
}
NOTE: Only a part of code(where I'm facing the problem) is provided here. Its a DB first approach and in no way I can alter that (ORDERS).
I tried the above, but all of the ids are posted to the controller no matter how many checkboxes are selected.
Question: How should I bind the IEnumerable<int> ImageIdList property to a checkbox in View.cs and post the data to Controller.cs so that only the ids of selected checkboxes are posted?
This is a nice practice... it will work and Iam working with such a
manner (Iam sure that it will work very well) but one thing you have to be very carefull while coding this, little bit
complicated
Iam taking this effort not only for as an answer to this particular question.
Its for all stackoverflow users. Because i never found the below method anyware in stackoverflow.
I get this method by a long search. You people can use this.
It will help you to avoid for loops to bind the Checkboxlist
Its the best good for re-usability (need a single line (max: 20-25 chars to bind a CheckBoxList in Razor))
CheckBoxListItem.cs
create a New Class CheckBoxListItem //you can use any other names
public class CheckBoxListItem
{
public int ID { get; set; }
public string Display { get; set; }
public bool IsChecked { get; set; }
}
MyModel.cs
This is modelclass
public class MyModel
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public IEnumerable<CheckBoxListItem> ChkList { get; set; }
}
HomeController.cs
This is controller
public ActionResult Index()
{
var model = new MyModel(){
Id = 0,
Name = "Your Name",
ChkList = dbContext.myTable.Select(x => new CheckBoxListItem { ID = x.MyTableFieldID, Display = x.MyTableFieldName, IsChecked = true })
//If you need only int part, then just avoid to bind data on Display field
};
return View(model);
}
[HttpPost]
public ActionResult Index(MyModel myModel) //Your model object on PostAction
{
IEnumerable<CheckBoxListItem> ChkList = myModel.ChkList;
// Here is your answer, You can see all your check box items (both checked and unchecked) in ChkList, it will shows all your checked items are true and non-checked items are false in IsChecked field
}
Here you have to give more patiance
Goto the Folder View>Shared>EditorTemplates and RightClick Add>View... and Create a new View with the same name CheckBoxListItem.cshtml
CheckBoxListItem.cshtml
#model Project.Models.CheckBoxListItem
<div class="">
#Html.HiddenFor(x => x.ID)
<div class="">
#Html.CheckBoxFor(x => x.IsChecked)
</div>
#Html.LabelFor(x => x.IsChecked, Model.Display, new { #class = "" })
</div>
Create your View
Index.cshtml
#model #model Project.Models.MyModel
<div class="form-group">
#Html.LabelFor(model => model.Id, htmlAttributes: new { #class = "" })
<div class="col-md-10">
#Html.EditorFor(model => model.Id, new { htmlAttributes = new { #class = "" } })
#Html.ValidationMessageFor(model => model.Id, "", new { #class = "" })
</div>
</div>
#Html.EditorFor(model => model.ChkList) //This only one line of code is enough to bind a checkBoxList in future
<input type="submit" value="Create" class="" />
You will get all these in your post action

File Upload with Partial View MVC 5

I'm attempting to upload a number and a file through an Html-Helper form in my MVC 5 application. After submitting the model, it seems that my model.File property from the view model always returns null.
Before adding the model.File != null check, my application was throwing a NullReferenceException.
I have my form logic contained in a partial view called UploadFile.cshtml, since my form logic requires a different model than my index.cshtml.
How do I read the File property from my form in my [HttpPost] controller action?
index.cshtml
#Html.Action("UploadFile", "Home")
UploadFile.cshtml
#model FileViewModel
#using (Html.BeginForm("FormUpload", "Home", FormMethod.Post))
{
<div class="form-group">
#Html.TextBoxFor(m => m.Marker)
</div>
<div class="form-group">
#Html.TextBoxFor(m => m.File, new { type = "file" })
</div>
<input type="submit" name="submit" value="Submit" />
}
HomeController
public PartialViewResult UploadFile()
{
return PartialView("UploadFile", new FileViewModel());
}
// /Home/FormUpload
[HttpPost]
public ActionResult FormUpload(FileViewModel model)
{
if (ModelState.IsValid)
{
if (model.File != null && model.File.ContentLength > 0 && model.File.ContentType == "application/pdf")
{
// Convert file to byte[] and upload
// ...
ViewBag.Message = "File Uploaded Successfully";
}
else
{
ViewBag.Message = "File Not Uploaded";
}
}
return RedirectToAction("Index");
}
FileViewModel
public class FileViewModel
{
public int ID { get; set; }
public int Marker { get; set; }
public string Filename { get; set; }
public HttpPostedFileBase File { get; set; }
}
You are missing enctype attribute for the form, you need to specify that for file case:
#using (Html.BeginForm("FormUpload", "Home",
FormMethod.Post,
new { enctype = "multipart/form-data" }))
See this article for further details.
Html.BeginForm("FormUpload", "Home", FormMethod.Post))
should be
#using (Html.BeginForm("FormUpload", "Home", FormMethod.Post, new {enctype = "multipart/form-data"}))

ASP.NET MVC validating model

I am having some difficulties in validation of form.
Since I want to populate combobox from database (another table, I'm using viewbags to do this), is there a way to use ComboBoxFor in this case so I could use System.ComponentModel.DataAnnotations and jquery.validate.js?
View:
#model MyProject.OpenAccess.Document
#using (Html.BeginForm("CreateDocument", "Create", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<label>Select file:</label><br />
<input type="file" name="file" /><br />
<label>Filetype</label><br />
#Html.DropDownList("fileType", (IEnumerable<SelectListItem>)ViewBag.ListFiletypes, "-Filetypes", new { #class = "filetype-cb" }) <br />
<input type="submit" value="Add"/>
}
Controller:
public ActionResult CreateDocument(HttpPostedFileBase file, string fileType)
{
if (file != null && file.ContentLength > 0)
{
Document doc = new Document()
{
Filetype = fileType
}
if (this.TryValidateModel(doc))
{
this.dbContext.Add(doc);
this.dbContext.SaveChanges();
id = doc.ID;
//save document using document ID
}
}
}
EDIT:
Here is my current implementation of this:
Layout.cshtml
Remember to add:
<script src="#Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>
View
#model MyProject.OpenAccess.Document
#using (Html.BeginForm("CreateDocument", "Create", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<label>Select file:</label><br />
<input type="file" data-val="true" data-val-required="Please select a file!" name="file" /><br />
<label>Filetype</label><br />
#Html.DropDownListFor(m => m.FileType, (IEnumerable<SelectListItem>)ViewBag.ListFiletypes, "", new { #class = "filetype-cb" })
<input type="submit" value="Add"/>
}
Controller
[HttpGet]
public ActionResult CreateDocument()
{
ViewBag.ListFiletypes = new SelectList(this.dbContext.ListFiletypes.ToList(), "FileType", "FileType");
//Populate dropdownlist from database
}
[HttpPost]
public ActionResult CreateDocument(HttpPostedFileBase file, Document doc)
{
if (file != null && file.ContentLength > 0)
{
if (this.TryValidateModel(doc))
{
this.dbContext.Add(doc);
this.dbContext.SaveChanges();
id = doc.ID;
//save document using document ID
}
}
}
Model
public partial class Document
{
private int _iD;
public virtual int ID
{
get
{
return this._iD;
}
set
{
this._iD = value;
}
}
private string _fileType;
[Required(ErrorMessage = "Required!")]
public virtual string FileType
{
get
{
return this._fileType;
}
set
{
this._fileType = value;
}
}
}
Is there good reasons to use Viewmodel over Viewbag to populate DropDownListFors (found this from http://www.codeproject.com/Articles/687061/Multiple-Models-in-a-View-in-ASP-NET-MVC-MVC)?
Only problem with this implementation is that I can't validate file on client-side (so user couldn't post empty file).
EDIT: Got this working by just adding:
<input type="file" data-val="true" data-val-required="Please select a file!" name="file" />
Normally you have 3 different parts, which build a proper implementation of your scenario.
The ViewModel:
In a proper implementation, you got for every view its own Viewmodel. In your case that would be something like this:
public class CreateDocumentViewModel
{
[Required]
public IEnumerable<SelectListItem> filetypes { get; set; }
// Maybe some more attributes you need in the view?
}
Note: here you can use the DataAnnotations you want.
The View:
The view contains the actual data in the dropdown.
#model CreateDocumentViewModel
#using (Html.BeginForm("CreateDocument", "Create", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<label>Select file:</label><br />
<input type="file" name="file" /><br />
<label>Filetype</label><br />
#Html.DropDownListFor(model => model.filetypes, model.filetypes, "-Filetypes", new { #class = "filetype-cb" })
<br />
<input type="submit" value="Add"/>
}
The controller:
The controller you need, has 2 actions. A GET- and a POST-action.
[HttpGet]
public ActionResult CreateDocument()
{
CreateDocumentViewModel model = new CreateDocumentViewModel();
model.filetypes = FillFileTypesFromDB; // Here you fill the data for the dropdown!
return View(model);
}
Finally you need the POST-action which saves back to your db.
[HttpPost]
public ActionResult CreateDocument(HttpPostedFileBase file, string fileType)
{
if (file != null && file.ContentLength > 0)
{
Document doc = new Document()
{
Filetype = fileType
}
if (this.TryValidateModel(doc))
{
this.dbContext.Add(doc);
this.dbContext.SaveChanges();
id = doc.ID;
//save document using document ID
}
}
}
I hope I fully understood your problem and it helps solving it.

Aggregate all my textbox values into a list before posting the form

I am building an MVC form with a bunch of textboxes. However, my model requires a list of strings. What I'm trying to do is have it so when the user hits submit, before the form posts, it takes all of the values in the text box, adds it to a List and appends it to the model for submitting. I'm stuck at how I would be able to do this?
The reason I need to do this is that I need the form to be dynamic in that the # of fields can grow and the page adapts to that. I can't hardcode the individual fields into the model as the model would need to adapt to the length of the form.
Here's my code so far:
The HTML
#using (Html.BeginForm(new { id = "feedbackForm" }))
{
<fieldset>
#foreach (Feedback_Num_Questions questionForm in FeedbackSurveyQuestions)
{
string textBoxID = "feedbackq";
questionNumberTextBoxes++;
textBoxID = textBoxID + questionNumberTextBoxes.ToString();
<input type="text" id="#textBoxID" />
}
<input type="text" id="txtAdditionalComments" />
<input type="submit" value="Create" name="btnFeedbackSubmit" id="btnSubmitFeedbackForm" />
</fieldset>
}
The Model:
public class PostCourseFeedbackModel
{
public List<string> responseList { get; set; }
public decimal pelID { get; set; }
public string employeeid { get; set; }
}
Thanks for the help!
UPDATE: after getting a response from FLEM, I revised my code a bit to this. Still not quite working, but was hoping somebody could spot out the issue:
HTML:
#using (Html.BeginForm(new { id = "feedbackForm" }))
{
<fieldset>
#{
int i = 0;
foreach (Feedback_Num_Questions questionForm in FeedbackSurveyQuestions)
{
i++;
string textBoxID = "feedbackq" + i.ToString();
#Html.TextBoxFor(m => m.responseList[i], new { #id = textBoxID });
}
<input type="submit" value="Create" name="btnFeedbackSubmit" id="btnSubmitFeedbackForm" />
}
</fieldset>
}
The model remained unchanged...
UPDATE - As requested by flem, I am posting my action. However this is a barebones action as I have yet to code what I plan to do with the data...
[HttpPost]
public ActionResult PostCourseFeedback(PostCourseFeedbackModel feedback)
{
return RedirectToAction("FeedbackSubmitted", "Feedback", new { status = "success" });
}
I would let the model binder do that hard work for you.
#using (Html.BeginForm(new { id = "feedbackForm" }))
{
<fieldset>
#{
int i = 0;
foreach (Feedback_Num_Questions questionForm in FeedbackSurveyQuestions)
{
#Html.EditorFor(m => m.responseList[i++])
}
}
<input type="text" id="txtAdditionalComments" />
<input type="submit" value="Create" name="btnFeedbackSubmit" id="btnSubmitFeedbackForm" />
</fieldset>
}
Here's a working sample:
Model and controller:
using System.Collections.Generic;
using System.Web.Mvc;
namespace MvcApplication1.Controllers
{
public class MyController : Controller
{
public ActionResult MyAction(MyModel Model)
{
return View(Model);
}
}
public class MyModel
{
public List<string> TextFields { get; set; }
}
}
View:
#model MvcApplication1.Controllers.MyModel
#using (Html.BeginForm())
{
for (int i = 0; i < 3; i++)
{
string id = "txt" + i.ToString();
#Html.TextBoxFor(m => m.TextFields[i], new { id = id })
}
<input type="submit" value="Post" />
if (Model.TextFields != null)
{
foreach (string textField in Model.TextFields)
{
<div>Posted Field: #textField</div>
}
}
}
I believe that if you modify the id and the name attributes of your textboxes and your post action is expecting your model class as its parameter you should be able to do something like the following when you create your textboxes:
<input type="text" id="id="responseList_#textBoxID__" name="responseList[#textBoxID]" />

Categories