Why HttpPostedFile is coming null in action method from view? - c#

I am trying to upload a file but while saving it throws error in action i.e. object reference not set and also the value is NULL.
It's the same class that is getting passed and saved but stil it throws error. Why it's null? It is binded to the same viewmodel.
#model VAILCertificates.UI.ViewModels.AddInspectionReportViewModel
#using (Html.BeginForm("Create", "InspectionReport", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<div class="form-group">
#Html.LabelFor(model => model.File, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-5">
#Html.TextBoxFor(model => model.File, new { #class = "form-control", type = "file" })
#Html.ValidationMessageFor(model => model.File, "", new { #class = "text-danger" })
</div>
</div>
<button class="btn btn-success btn-outline btn-block" type="submit"><i class="fa fa-save" style="font-size:medium"></i> Add</button>
}
Action:
public ActionResult Create(AddInspectionReportViewModel model, HttpPostedFileBase FileName)
{
string FilePath = Server.MapPath("~/Downloads/Certificates/");
model.File.SaveAs(FilePath + Path.GetFileName(model.File.FileName));
model.InspectionReport.FileName = Path.GetFileName(model.File.FileName);
InspectionReportDAL.AddInspectionReport(model.InspectionReport);
return RedirectToAction("Index");
}
Viewmodel:
public class AddInspectionReportViewModel
{
public HttpPostedFile File { get; set; }
public InspectionReport InspectionReport { get; set; }
}
Class:
public class InspectionReport
{
//General Details
public int InspectionReportID { get; set; }
public string FileName { get; set; }
}
Update: the internal error is
The parameter conversion from type 'System.Web.HttpPostedFileWrapper' to type 'System.Web.HttpPostedFile' failed because no type converter can convert between these types.

HttpPostedFileBase FileName
model.File
The parameter name the method is expecting is FileName.
Edit this #Html.TextBoxFor(model => model.File, new { #class = "form-control", type = "file" })
to
#Html.TextBoxFor(model => model.File.FileName , new { #class = "form-control", type = "file" })
Based on the comments I would do the following if possible.
If I were you I would remove the #Html.TextBoxFor(model => model.File, new { #class = "form-control", type = "file" }) and replace it with:
<input type="file" name="FileName" id="fileUpload or whatever" accept="//Whatever file you want to accept">
At the current state you are never passing anything to the HttpPostedFileBase parameter, just the model and so it is always null.

Change viewModel to:
public class AddInspectionReportViewModel
{
[DataType(DataType.Upload)]
public HttpPostedFileBase File { get; set; }
public InspectionReport InspectionReport { get; set; }
}
change view to:
#using (Html.BeginForm("Create", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<div class="form-group">
#Html.LabelFor(model => model.File, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-5">
#Html.TextBoxFor(m => m.File, new { type = "file" })
#Html.ValidationMessageFor(model => model.File, "", new { #class = "text-danger" })
</div>
</div>
<button class="btn btn-success btn-outline btn-block" type="submit"><i class="fa fa-save" style="font-size:medium"></i> Add</button>
}

Changing HttpPostedFile to HttpPostedFileBase has worked.
public class AddInspectionReportViewModel
{
public HttpPostedFileBase File { get; set; }
public InspectionReport InspectionReport { get; set; }
}

Related

ASP.NET MVC POST won't send back second ocurrence of variable

The class Unidade:
public class Unidade
{
public int UnidadeId { get; set; }
public string Apelido { get; set; }
public string Descricao { get; set; }
}
Is used twice in the class Insumo, as Unidade and UnidadeConsumo
public class Insumo
{
public int InsumoId { get; set; }
public string Apelido { get; set; }
public string Descricao { get; set; }
public int UnidadeId { get; set; }
public Unidade Unidade { get; set; }
public int UnidadeConsumoId { get; set; }
public Unidade UnidadeConsumo { get; set; }
}
To edit Insumo there are two actions EDIT in the controller:
public ActionResult Edit(int? id)
{
Insumo insumo = db.Insumos.Find(id);
if (insumo == null) return HttpNotFound();
ViewBag.UnddId = new SelectList(db.Unidades, "UnidadeId", "Apelido", insumo.UnidadeId);
ViewBag.UndConsId = new SelectList(db.Unidades, "UnidadeId", "Apelido", insumo.UnidadeConsumoId);
return View(insumo);
}
And the POST EDIT:
[HttpPost]
public ActionResult Edit([Bind(Include = "InsumoId,Apelido,Descricao,UnidadeId,UnidadeConsumoId")] Insumo insumo)
{
if (ModelState.IsValid)
{
db.Entry(insumo).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.UnddId = new SelectList(db.Unidades, "UnidadeId", "Apelido", insumo.UnidadeId);
ViewBag.UndConsId = new SelectList(db.Unidades, "UnidadeId", "Apelido", insumo.UnidadeConsumoId);
return View(insumo);
}
The view to display the fields to edition, comprised of two dropdown lists to select both units is:
#model Gestor.Models.Insumo
#{
ViewBag.Title = "Alterar";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Alterar</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Insumo</h4>
<hr />
#Html.Partial("CopyEdit")
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Gravar" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Retornar a lista", "Index")
</div>
And the partial view CopyEdit in the center:
#model Gestor.Models.Insumo
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.InsumoId)
<div class="form-group">
#Html.LabelFor(model => model.Apelido, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Apelido, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Apelido, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Descricao, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Descricao, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Descricao, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.UnidadeId, "Unidade", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("UnddId", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.UnidadeId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.UnidadeConsumoId, "Unidade de Consumo", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("UndConsId", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.UnidadeConsumoId, "", new { #class = "text-danger" })
</div>
</div>
The problem is thta whrn returned to the POST Edit action, all fields are ok, but UnidadeConsumoId is always 0, what doesn't even exists in the database?
Can someone tell me why is it not returning the expected value, i.e. the selected value in the dropdown list reflecting the id of if?
Because your current code is rendering a SELECT element with name attribute value UndConsId.
<select class="form-control" name="UndConsId">
<!-- options -->
</select>
For model binding to work, the input element name attribute value should match with the parameter/property name used in the http post action method. Your parameter name is UnidadeConsumoId, not UndConsId
To fix this issue, pass UnidadeConsumoId as the first parameter of the DropDownList method call so that it will render the SELECT element with name UnidadeConsumoId. You can pass the ViewBag.UndConsId as the second parameter to explicitly specify the collection to be used to build the SELECT element.
This should work
#Html.DropDownList("UnidadeConsumoId", ViewBag.UndConsId as SelectList,
new { #class = "form-control" })

One of two model for View is always null in MVC 5

In my MVC5 application I have two model Order and File as following:
public class Order
{
public int OrderID { get; set; }
public string OrderName{ get; set; }
}
public class File
{
public HttpPostedFileBase[] files { get; set; }
}
I want edit objects of both classes in single view, so I create parent class:
public class MainContext
{
public Order Order { get; set; }
public File File { get; set; }
}
In the view I have this:
#using (Html.BeginForm("Create", "Order", FormMethod.Post, new { encType = "multipart/form-data" }))
#Html.AntiForgeryToken()
<div class="form-group">
<label>OrderName</label>
#Html.EditorFor(model => model.Order.OrderName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Order.OrderName, "", new { #class = "text-danger" })
</div>
<div class="form-group">
#Html.LabelFor(model => model.File.files, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextBoxFor(model => model.File.files, "", new { #type = "file", #multiple = "multiple", })
#Html.ValidationMessageFor(model => model.File.files, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<input type="submit" value="submit" class="btn btn-success btn-lg btn-block" />
</div>
The Controller
public ActionResult Create()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "OrderName")] Order order, HttpPostedFileBase[] files)
{
if (ModelState.IsValid)
{
db.Order.Add(order);
db.SaveChanges();
if (files != null)
{
foreach (HttpPostedFileBase file in files)
{
if (file != null)
{
var InputFileName = Path.GetFileName(file.FileName);
var ServerSavePath = Path.Combine(Server.MapPath("~/UploadedFiles/") + InputFileName);
file.SaveAs(ServerSavePath);
}
}
}
return RedirectToAction("Index");
}
}
Now the problem .. after I submit the form I got order values in Create action BUT files is always NULL !
What I miss
Thanks in advance
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(MainContext model)
{
// here fetch values from model.Order and model.File
}
Instead of fetching two models separately call "MainContext" class in post action, from that you can get all the view values....

How to post objects in ViewModel?

I've got this viewmodel class with an integer Id and an object type.
public class MyViewModel
{
[Required]
public int MyId { get; set; }
public MyObject MyObject { get; set; }
}
MyObject Model is:
public class MyObject
{
[Key]
public int ObjId { get; set; }
public int Number { get; set; }
public string Name { get; set; }
}
This controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "MyId, MyObject.Number, MyObject.Name")]MyViewModel vm)
{
if (!ModelState.IsValid)
{
return View();
}
//do something!
}
The view is:
#model MyProject.Models.MyViewModel
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-group">
#Html.LabelFor(model => model.MyId , htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("MyId", null, "Select", htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.MyId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.MyObject.Number, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextBoxFor(model => model.MyObject.Number, new { #class = "form-control", type = "number", min = "1", max = "3", step = "1" })
#Html.ValidationMessageFor(model => model.MyObject.Number, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.MyObject.Name , htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextBoxFor(model => model.MyObject.Name , new { #class = "form-control", type = "number", min = "1", max = "3", step = "1" })
#Html.ValidationMessageFor(model => model.MyObject.Name , "", 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" formaction="#Url.Action("Create")" />
</div>
</div>
}
When I post data to controller, it recognize the MyId value but don't fill MyObject parameter. Any suggests to how to post an object with ViewModel?
Assuming that you have #model MyViewModel at the top of the view, there's no need to complicate it.
Make your submit on the view
<input type="submit" value="Create" class="btn btn-default" />
and change the signature of the post method
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(MyViewModel vm)
{
if (!ModelState.IsValid)
{
return View();
}
//do something!
}
The view model should then be bound.
Try below:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "MyId,MyObject, MyObject.Number, MyObject.Name")]MyViewModel vm)
{
if (!ModelState.IsValid)
{
return View();
}
}
You aren't including MyObject in Bind(include)

How do I use two dropdown menu instances from the same model?

I am having an issue where I think I have set things up correctly, however the results are alway nil.
Here's the three models:
public class FamilyMember
{
[Key]
public int id { get; set; }
[Required]
[Display(Name = "First Name")]
public string firstName { get; set; }
[Required]
[Display(Name = "Surname")]
public string surname { get; set; }
[Required]
[Display(Name = "Date of Birth")]
public DateTime dob { get; set; }
public virtual FamilyRelationship FamilyRelationship { get; set; }
[Display(Name = "Full Name")]
public string fullName
{
get
{
return string.Format("{0} {1}", firstName, surname);
}
}
}
public class RelationshipType
{
[Key]
public int id { get; set; }
[Required]
[Display(Name="Relationship Type")]
public string relationshipType { get; set; }
public virtual FamilyRelationship FamilyRelationship { get; set; }
}
public class FamilyRelationship
{
[Key]
public int id { get; set; }
[ForeignKey("FamilyMembers")]
[Display(Name = "First Family Member")]
public int familyMemberPrimary { get; set; }
[ForeignKey("FamilyMembers")]
[Display(Name = "Second Family Member")]
public int familyMemberSecondary { get; set; }
[Display(Name = "Relationship Type")]
public int relationshipType { get; set; }
public virtual ICollection<FamilyMember> FamilyMembers { get; set; }
public virtual ICollection<RelationshipType> RelationshipTypes { get; set; }
}
So, I have successfully added data to FamilyMember and RelationshipType and the CRUD is working perfectly.
The problem is found in the Create Controller/View of FamilyRelationship. The dropdown works perfectly and shows the family members in the two associated menus and the relationship also shows on the relationType dropdown. However, when I click create all values are set to null.
Create Controller:
// GET: FamilyRelationships/Create
public ActionResult Create()
{
ViewBag.familyMember = new SelectList(db.FamilyMembers, "id", "fullName");
ViewBag.relationship = new SelectList(db.RelationshipTypes, "id", "relationshipType");
return View();
}
// POST: FamilyRelationships/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "id,familyMemberPrimary,familyMemberSecondary,relationshipType")] FamilyRelationship familyRelationship)
{
if (ModelState.IsValid)
{
db.FamilyRelationships.Add(familyRelationship);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(familyRelationship);
}
Create View:
#model FamilyTree.Models.FamilyRelationship
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>FamilyRelationship</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.familyMemberPrimary, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#*#Html.EditorFor(model => model.familyMemberPrimary, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.familyMemberPrimary, "", new { #class = "text-danger" })*#
#Html.DropDownList("familyMember", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.familyMemberPrimary, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.familyMemberSecondary, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("familyMember", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.familyMemberPrimary, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.relationshipType, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("relationship", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.relationshipType, "", 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>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
Please let me know where am I going wrong and if possible provide an example to make this work.
#Html.DropDownList("familyMember"
you need to use the actual property names most likely
#Html.DropDownList("familyMemberPrimary"
you'd also have to rename the viewbag property to match for the items to show up.. or use dropdownlistfor
#Html.DropDownListFor(a => a.familyMemberPrimary, (SelectList)ViewBag.familyMember , new { #class = "form-control" })
you also need to add a dropdownlist for familyMemberSecondary
#Html.DropDownListFor(a => a.familyMemberSecondary, (SelectList)ViewBag.familyMember , new { #class = "form-control" })
This should get you pretty close..
#model FamilyTree.Models.FamilyRelationship
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>FamilyRelationship</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.familyMemberPrimary, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(a => a.familyMemberPrimary, (SelectList)ViewBag.familyMember, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.familyMemberPrimary, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.familyMemberSecondary, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(a => a.familyMemberSecondary, (SelectList)ViewBag.familyMember, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.familyMemberSecondary, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.relationshipType, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(a => a.relationshipType, (SelectList)ViewBag.relationship, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.relationshipType, "", 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>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
make sure you re set your ViewBag properties after a failed POST
DotNetFiddle Example
This is the expected behaviour. Remember Http is stateless. So you need to reload your dropdown data before returning to the view
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "id,familyMemberPrimary,familyMemberSecondary,
relationshipType")] FamilyRelationship familyRelationship)
{
if (ModelState.IsValid)
{
db.FamilyRelationships.Add(familyRelationship);
db.SaveChanges();
return RedirectToAction("Index");
}
//Let's reload the data for dropdown.
ViewBag.familyMember = new SelectList(db.FamilyMembers, "id", "fullName");
ViewBag.relationship = new SelectList(db.RelationshipTypes, "id", "relationshipType");
return View(familyRelationship);
}
EDIT: As per comment
The values within the FamilyRelationship so familyMemberPrimary,
familyMemberSecondary and relationshipType have values of 0, where I
was expecting the id's of each of these would be passed over.
Because you are using EditorFor helper method for familyMemberPrimary property in your view. So if you are not filling a value in that input field, it is going to have default value(0 for int type)
If you want that property to be filled with your dropdown selection(of family members), you should give the dropdown name value as familyMemberPrimary so that when you post the form, model binding will set the selected option value to familyMemberPrimary property.
#Html.DropDownList("familyMemberPrimary",
ViewBag.familyMember as IEnumerable<SelectListItem>,
htmlAttributes: new { #class = "form-control" })

Passing Image to Controller ASP.NET MVC

I am trying to send Image and ImageName from View to Controller.
Here's how my controller looks:
[HttpPost]
public ActionResult Add(BoxAddViewModel image)
{
//TODO: Add new box
return RedirectToAction("Index");
}
This is the model:
public class BoxAddViewModel : BaseViewModel
{
public int Id { get; set; }
[Required]
[DisplayName("Name")]
public string Name { get; set; }
[Required]
[DisplayName("Source")]
public HttpPostedFileBase Source { get; set; }
}
And finally the view:
#using (Html.BeginForm("Add", "BoxManagement", new { #class = "form-horizontal", enctype = "multipart/form-data" }))
{
<div class="form-group">
#Html.LabelFor(m => m.Name, new { #class = "col-sm-2 control-label" })
<div class="col-sm-10">
#Html.TextBoxFor(m => m.Name, new { #class = "form-control", #name = "Name"})
#Html.ValidationMessageFor(m => m.Name)
</div>
#Html.LabelFor(m => m.Source, new { #class = "col-sm-2 control-label" })
<div class="col-sm-10">
<!--Html.TextBoxFor(m => m.Source, new { type = "file",}) -->
#Html.ValidationMessageFor(m => m.Source)
<input type="file" name="Source" id="Source" />
</div>
</div>
<button type="submit" class="btn btn-primary"><span class="glyphicon glyphicon-plus"></span>Add</button>
#Html.ActionLink("Cancel", "Index", null, new { #class = "btn btn-default" })
}
It comes into the Add method and the Name property value is correct but the Source is null.
Anyone know how to solve this?
Your using the wrong overload of BeginForm() and adding route values, not html attributes (always inspect the html your generating). Use
#using (Html.BeginForm("Add", "BoxManagement", FormMethod.Post, new { #class = "form-horizontal", enctype = "multipart/form-data" }))
Side note: Remove new { #name = "Name"} from your TextBoxFor() method. Never attempt to override the name attribute generated by the HtmlHelper methods unless you want binding to fail (and is this case it does nothing anyway)

Categories

Resources