File upload bound to the Viewmodel - asp.net-mvc

I have a form where I am uploading multiple files and there are a couple of textboxes and some checkboxes associated with each file being uploaded.
I have seen examples for uploading multiple files where the actionresult signature is something like this:
[HttpPost]
public ActionResult Upload(IEnumerable<HttpPostedFileBase> fileUpload)
However, I cant find any example where I can have multiple files uploaded where my actionresult signature is something like this:
[HttpPost]
public ActionResult Upload(MyViewModel vm)
The reason I want this viewmodel being posted is because I think its cleaner than using the FormCollection variable and because I want to each file being uploaded and the data added along with its associated textboxes to be grouped together by way of a List<FileUploadPacket> which will part of the ViewModel
UPDATE
My View model below:
public class EmployeeVM
{
public int EmployeeID {get ;set;}
public string EmpName {get ;set;}
//Other properties
public List<FileUploadPacket> FileUploadPackets { get; set; }
}
The FileUploadPacket class which has the property of type HttpPostedFileBase
public class FileUploadPacket
{
public int FileID {get ;set;}
public string UserEnteredDesc {get ;set;}
//some more other properties
public HttpPostedFileBase UpFile { get; set; }
}
Code snippet of my view.aspx as below
<%: Html.TextBoxFor(model => model.EmpName, new { maxLength = 50 })%>
Upload your files here:
<input type="file" id="UpFile" name="UpFile" value="ActionHandlerForForm" />
<%: Html.TextBoxFor(model => model.FileUploadPackets[0].UserEnteredDesc )%>
<input type="file" id="UpFile" name="UpFile" value="ActionHandlerForForm" />
<%: Html.TextBoxFor(model => model.FileUploadPackets[1].UserEnteredDesc )%>
As you can see, I have all the other properties specific to this one file being uploaded kept in its own class. So that in my form an employee can enter his name and upload his files and provide some description and other details for each file. If I move the public HttpPostedFileBase UpFile { get; set; } property to the EmployeeVM class then I will have to collect all the files separately in an array and manually map a file to its description. Is there no way to do this keeping the UpFile property in the FileUploadPacket class itself?
I am using the aspx view engine.
Please help. Thanks for your time...

GetHtml helper is not part of mvc framework, you should look up for third party library containing that helper.
Uploading file that is part of ViewModel is simple though. Basically it goes like this
Define view model
public class MyViewModel
{
public HttpPostedFileBase MyFile { get; set; }
}
Inside Views/Shared/EditorTemplates, create MyViewModel.cshtml
<input type="file" id="MyFile" name="MyFile" />
And view, corresponding to upload action
#model MyViewModel
#using(Html.BeginForm("Upload", "MyController", FormMethod.Post, new { enctype="multipart/form-data"})
{
#Html.EditorForModel()
<input type="submit" value="Upload" />
}
required attribute is important to upload files.
And that's it, once form is submitted, you should see uploaded file inside [HttpPost] action, vm.MyFile.

The fix to this is changing the way you Name and ID the upload control.
<%: Html.TextBoxFor(model => model.EmpName, new { maxLength = 50 })%>
Upload your files here:
<input type="file" id="FileUploadPackets[0].UpFile" name="FileUploadPackets[0].UpFile" value="ActionHandlerForForm" />
<%: Html.TextBoxFor(model => model.FileUploadPackets[0].UserEnteredDesc )%>
<input type="file" id="FileUploadPackets[1].UpFile" name="FileUploadPackets[1].UpFile" value="ActionHandlerForForm" />
<%: Html.TextBoxFor(model => model.FileUploadPackets[1].UserEnteredDesc )%>
This worked for me!! Hope it helps anyone else out there..

Related

List Binding with model data

So I have a form that I am trying to submit and I can get either the list or the model to bind, but not both at the same time. I suspect it has to do with the model binder.
HTML
#using (Html.BeginForm("Index", "Home", FormMethod.Post)){
#Html.AntiForgeryToken()
<div class="TransferHeader">
<div>
#Html.LabelFor(model => model.tranRequestedBy)
#Html.EditorFor(model => model.tranRequestedBy, new { #Name = "h.tranRequestedBy" })
</div>
<div>
#Html.LabelFor(model => model.tranNotes)
#Html.EditorFor(model => model.tranNotes, new { #Name = "h.tranNotes" })
</div>
<input name="h.TransfersDetail.Index" id="detIndex" type="hidden" value="c3a3f7dd-41bb-4b95-b2a6-ab5125868adb">
<input name="h.TransfersDetail[c3a3f7dd-41bb-4b95-b2a6-ab5125868adb].detToolCode" id="detToolCode" type="hidden" value="1234">
</div>
}
Controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(TransfersHeader h)
{
return View();
}
Model Class:
public virtual ICollection<TransfersDetail> TransfersDetail { get; set; }
public string tranRequestedBy { get; set; }
public string tranNotes { get; set; }
The two bottom inputs were generated from an AJAX call to an add method, what happens is if they are not present the two HTML helper editors will come in the model, but if they do exist only the transfer detail list will appear.
Is there anything I could do to make sure all of the data comes into the model?
Its not clear how you are generating those inputs, but the name attributes are incorrect. You model does not contain a collection property named h, but it does contain one named TransfersDetail, so your inputs need to be
<input name="TransfersDetail.Index" type="hidden" value="c3a3f7dd-41bb-4b95-b2a6-ab5125868adb">
<input name="TransfersDetail[c3a3f7dd-41bb-4b95-b2a6-ab5125868adb].detToolCode" type="hidden" value="1234">
Its also not clear why your adding an id attribute (if you referencing collection items in jQuery, you would be better off using class names and relative selectors), but the id your using does not have an indexer suggesting that your going to be generating duplicate id attributes which is invalid html (and jQuery selectors would not work in any case)

MVC 3 fileupload dialog

I think those tags pretty much says what i'm asking..
I have been struggling with file upload. What I need to achieve is open a dialog for file upload and save it to database, so nothing too fancy.
Basic file upload is more than easy to make. Just form with correct encrypt and input type file. But when I insert my form to dialog something goes wrong and there is nothing in Post. I tried to add test parameters like filename and it worked fine. But the actual file is missing in post.
here's some code:
Form:
#using (Html.BeginForm("Edit", "Home", FormMethod.Post,
new { enctype = "multipart/form-data" })){
<label for="Name">Filename: </label>
<input type="text" name="name" id="name"/>
<input type="file" name="file" id="file" />
<input type="submit"/>
}
Controller:
public ActionResult Edit(Attachment model)
{
var strLen = Convert.ToInt32(model.file.InputStream.Length);
var strArr = new byte[strLen];
model.file.InputStream.Read(strArr, 0, strLen);
return View();
}
Edit:
Model:
public class Attachment
{
public string Name { get; set; }
public HttpPostedFileBase file{ get; set; }
}
This form is inside the dialog.
Try this,
public ActionResult Edit(HttpPostedFileBase file)
{
////
return View();
}

Help with asp.net mvc nested model binding

So I have this demo project almost completely working.
public class Project
{
public int ID { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<Task> Tasks { get; set; }
}
public class Task
{
public int ID { get; set; }
[Required]
public string Name { get; set; }
public int ProjectID { get; set; }
public virtual Project Project { get; set; }
}
Controller
public ActionResult Edit(int id)
{
var project = db.Projects.Where(p=>p.ID==id).Single();
return View(project);
}
[HttpPost]
public ActionResult Edit(Project project)
{
if (ModelState.IsValid)
{
var dbProject = db.Projects.Where(p => p.ID == project.ID).Single();
UpdateModel(dbProject);
db.SaveChanges();
TempData["Success"] = "Modelo Valido";
}
return RedirectToAction("Index");
}
View//strongly typed for project
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Project</legend>
#Html.HiddenFor(model => model.ID)
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<h1>Tasks</h1>
#Html.EditorFor(m => m.Tasks)
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
EditorTemplate
#model MvcApplication2.Models.Task
<span>Task</span>
<br />
#Html.LabelFor(m => m.Name)
#Html.EditorFor(m => m.Name)
#Html.HiddenFor(m => m.ID)
#Html.HiddenFor(m => m.ProjectID)
#Html.ValidationMessageFor(m => m.Name)
The view displays this
The problem is that when I submit the form the Tasks are populated with everything except the virtual Project property... so the error i get it is
The operation failed: The relationship could not be changed because
one or more of the foreign-key properties is non-nullable. When a
change is made to a relationship, the related foreign-key property is
set to a null value. If the foreign-key does not support null values,
a new relationship must be defined, the foreign-key property must be
assigned another non-null value, or the unrelated object must be
deleted.
Here is a pic of my debugging breakpoint result
Please Help.
UPDATE:
I have changed my controller action to this
[HttpPost]
public ActionResult Edit(Project project)
{
if (ModelState.IsValid)
{
db.Entry(project).State = EntityState.Modified;
db.SaveChanges();
TempData["Success"] = "Modelo Valido";
return RedirectToAction("Index");
}
return View(project);
}
it is still not working correctly.
Now changes made to the Name of the project are updated correctly in the database. but changes made to any Task Name are ignored completely.
i believe #Html.EditorFor(m => m.Tasks) is generating html like (approximately)
<label>Name</label>
<input type="text" name="Tasks[0].Name" id="auto-gen-id"/>
<input type="hidden" name="Tasks[0].ID" id = "auto-gen-id"/>
<input type="hidden" name="Tasks[0].ProjectID" id = "auto-gen-id"/>
<!--html for validation span-->
Above is the approximate html generated for first Task in Collection and similar html will be generated for each task in the collection. The only difference is that index will be incremented in name attributes of all inputs i.e Tasks[1].Name, Tasks[1].ProjectID etc. This portion will actually bind to the Collection<Task> Tasks property of Project but you can see that in detail portion you don't have any inputs like
<input type="whatever" name="Tasks[0].Project.ProjectID" .../>
<input type="whatever" name="Tasks[0].Project.Name" ..../>
Modelbinder needs input elements with proper naming conventions to bind values to all properties of action method parameters. For testing purpose you can inlude these two lines in your Editor template for Task
#Html.TextBoxFor(x=>x.Project.ID)
#Html.TextBoxFor(x=>x.Project.Name)
input proper values for them in the form and you will have Project property of Task populated with these values. But may not be what you desire i.e entering project information twice and this may not be needed (if u are using Linq to sql its sure not needed). When you call your ORM for attaching entities to db entities it will know which Project elements, current Task belongs to.
Side Note: When you have problems with modelbinding, always pay attention to generated html. Generated html will dictate which form values will map to which properties of the model as long as you are using default modelbinder. it becomes especially important if you are having master detail kind of scenario as in your example.
I have found a way to get this to work but Im not completely happy with the approach.
see this question on how to refactor my current code to see how I am currently (hopefully temporarily doing it)
Help improving (refactoring) my code. Automapper - EF - asp.net mvc-3

Not able to bind html.checkbox on form post

So I have a view model call ProductViewModel which has a list of sites where a product can be produced. I am trying to figure out how to show this on a form using a checkbox for the user to select the sites where the product can be produced. Seems straight forward, right? Well it doesn't work like I want/need. Hoping someone can help guide me to the correct way to do this.
My classes:
public class ProductViewModel
{
public List<Sites> SiteList {get; set;}
public string ProductName {get; set;}
public int ProductId {get; set;}
public User ProductOwner{get; set;}
}
public class Sites
{
public int SiteId {get; set;}
public string SiteName {get; set;}
public bool IsSelected {get; set;}
}
Part of my view:
#Html.LabelFor(m=>m.Sites):
#foreach (var site in Model.Sites)
{
#Html.CheckBox("Sites", site.IsSelected, new { value = site.SiteName })
#Html.Label(site.SiteName)
}
When using #Html.Checkbox() I see the following output in the html from the browser:
<input checked="checked" id="Sites" name="Sites" type="checkbox" value="Miami" />
<input name="Sites" type="hidden" value="false" />
I understand the hidden field but what I really need is to get the value for the selected item. So I need to get back the list with Miami in it. I don't need the false/true thing that the html helper seem to want to send (i.e. Miami=true)
So instead I tried this.
#for(int id=0; id < Model.Sites.Count(); id++)
{
<input type="checkbox" id="#Model.Sites[id].SiteName" name="Sites[#id]" value="#Model.BoxingSites[id].SiteName" #(Model.Sites[id].IsSelected ? #"checked=""checked""": "") />
#Html.Label(Model.Sites[id].SiteName)
}
And the output is:
<input type="checkbox" id="Miami" name="Sites[0]" value="Miami" checked="checked" />
<label for="Miami">Miami</label>
In both of these cases I am not able to get the binder to map the form values to the Product.Sites list when posting to the action.
The action is like this:
[HttpPost]
public ActionResult Edit(ProductViewModel Product)
{
//Does something with the form data.
}
The other values (ProductName etc...) map fine.
What am I doing wrong? I feel I am missing something as this should be easier due to how MVC simplifies so many other form handling situations.
Thanks in advance...
How about using an editor template instead of struggling with loops:
#model ProductViewModel
#using (Html.BeginForm())
{
... some other form fields
#Html.LabelFor(x => x.SiteList)
#Html.EditorFor(x => x.SiteList)
<input type="submit" value="Create" />
}
and inside the corresponding editor template ~/Views/Shared/EditorTemplates/Sites.cshtml:
#model Sites
<div>
#Html.HiddenFor(x => x.SiteId)
#Html.CheckBoxFor(x => x.IsSelected)
#Html.LabelFor(x => x.SiteName)
</div>
Now not only that your view code is much more clean but proper names will be generated for the input fields so that the model binder will be able to bind the selected values back in the POST action.
[HttpPost]
public ActionResult Create(ProductViewModel model)
{
...
}
Here is what is working for me.
// View Model
[Display(Name="Boolean Property")]
[UIHint("booleancheckbox"]
public bool? booleanProperty;
View
// View
#Html.EditorFor(m => m.booleanProperty, new { #onclick = "Toggle(this);" })
Editor Template - add some more code to handle null values
// Editor Template booleancheckbox.cshtml
#model bool?
#{
labelText = ViewData.ModelMetadata.DisplayName != null ?
ViewData.ModelMetadata.DisplayName :
ViewData.ModelMetadata.PropertyName;
}
<label for="#ViewData.ModelMetadata.PropertyName">#labelText
#Html.CheckBox(string.Empty, Model.Value, ViewContext.ViewData)
</label>

ASP.NET MVC File Uploading

HI there,
My model (partial)
public class Document : HttpPostedFileBase
{
public string DocumentTitle { get; set; }
public string DocumentType { get; set; }
My action
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult AddDocumentToVault(Document model)
{
foreach (string upload in Request.Files)
{
if (!Request.Files[upload].HasFile()) continue;
_documentAggregator.Add(model);
_documentAggregator.Commit();
}
return PSDocumentVaultPartial();
}
File uploader
<% using (Html.BeginForm("AddDocumentToVault", "PersonalSpace", FormMethod.Post, new { enctype = "multipart/form-data" }))
{%>
<input type="file" id="Document" runat="server" name="Document"/>
<input id="AddDocument" type="submit" value="Upload" style="display:none"/>
<% } %>
The problem I am having is that when the AddDocument button is pressed it's passing an empty model to the action in my controller. And the base properties in HttpPostedFileBase give a System.NotImplementException.
Can anyone tell me what I need to do to correctly pass my model to my action?
It's an issue with HttpPostedFileBase and model binding. See ASP.NET MVC posted file model binding when parameter is Model

Resources