MVC Razor EditorFor Last Object in List - c#

I'm trying to display EditorFor for the last child Object in an collection. Below are the POCOs for the Order (Parent) and Hold (child collection):
public class Order
{
public int ID {get;set;}
public string Name {get;set;}
....
public virtual List<Hold> Holds { get; set; }
}
public class Hold
{
public int ID { get; set; }
public int OrderID { get; set; }
public virtual Order Order { get; set; }
public DateTime? When { get; set; }
public string Reason { get; set; }
}
Here's my attempt at creating an Order view that shows an Order and the last Hold if there is one present. I've commented out the last Hold attempt that doesn't work.
#model Order
#using (Html.BeginForm("Update", "Order", FormMethod.Post, new {}))
{
<div class="form-group row">
#Html.LabelFor(x => x.Name, new { #class = "col-xs-2" })
<div class="col-xs-10">
#Html.EditorFor(x => x.Name, new { #class = "form-control"})
</div>
</div>
<div class="form-group row">
<label class="col-xs-2">When</label>
<div class="col-xs-10">
#*#Html.EditorFor(x => x.Holds.Last().When, new {})*#
</div>
</div>
}
The Holds collection can also be null so doing Last() in that case will case an null exception even if that did work.
This seems like something simple and I have this pattern in a couple places in my database. Can anyone recommend a good way to handle this situation?
Thanks!

You should use a view model for this because you wont get a very good response in your HttpPost action when you post this back
public class OrderViewModel
{
public OrderViewModel()
{
Order = new Order();
Hold = new Hold();
}
public Order Order { get; set; }
public Hold Hold { get; set; }
}
public ActionResult Edit(int id)
{
var o = from o context.Order.Include("Holds").Single(id);
var model = new OrderViewModel()
{
Order = o
}
if (o.Holds.Count() > 0)
model.Hold = o.Holds.Last();
return View(model);
}
then just use EditorFors
#model OrderViewModel
#using (Html.BeginForm("Update", "Order", FormMethod.Post, new {}))
{
<div class="form-group row">
#Html.LabelFor(x => x.Order.Name, new { #class = "col-xs-2" })
<div class="col-xs-10">
#Html.EditorFor(x => x.Order.Name, new { #class = "form-control"})
</div>
</div>
<div class="form-group row">
<label class="col-xs-2>When</label>
<div class="col-xs-10">
#Html.EditorFor(x => x.Hold.When)
</div>
</div>
}

First, instead of using Last() you should use LastOrDefault() and then do proper null-checking. Last() raises an exception is nothing is found, while LastOrDefault simply returns null in that case.
Second, using Last() or LastOrDefault() will not generate the proper input names via EditorFor, so once you post, the modelbinder won't know what to do with the value. Instead, you need to use indexing:
#if (Model.Holds.Any())
{
var lastIndex = Model.Holds.Count() - 1;
<div class="form-group row">
<label class="col-xs-2">When</label>
<div class="col-xs-10">
#Html.EditorFor(x => x.Holds[lastIndex].When, new {})
</div>
</div>
}

Related

How to bind an object in a view to the controller?

This may be a dumb question but I'm kind of new with the razor. I'm trying to create a dynamic form. I have a list of object of fields and show them dynamically in my page. But, when I want to save the selected value of my field for a dropdown(example), I don't know how to save the object of my foreach to my model in the controller (I can save my value with no harm).
Index.cshtml:
<div class="row">
#foreach (var buildingBlock in buildingBlocks)
{
<div class="col-sm">
<div class="card">
<div class="card-body">
<h5 class="card-title">#buildingBlock.BuildingBlockTitle</h5>
#foreach (Test.Test.Models.BuildingBlockField buildingBlockField in buildingBlockFields)
{
<div class="form-group">
<label for="companyName">Company Name</label>
//I tried that but it's not working (Obviously :))
#Html.EditorFor(model => buildingBlockField)
#Html.DropDownListFor(model => model.buildingBlockFields[0].Values, buildingBlockField.OptionDetails, "Select Contract", new { #class = "selectpicker", multiple = "multiple" })
</div>
}
</div>
</div>
</div>
}
</div>
BuildingBlockField:
public class BuildingBlockField
{
public int BuildingBlockFieldID{ get; set; }
public int BuildingBlockID { get; set; }
public List<SelectListItem>? OptionDetails { get; set; }
public string FieldTitle { get; set; }
public FieldType Type { get; set; }
public bool IsMultiple { get; set; }
public int[] Values { get; set; }
public string Value { get; set; }
}
model controller:
public class ContractInformationsModel
{
public List<BuildingBlockField> buildingBlockFields { get; set; }
}
HomeController:
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.BuildingBlocks = Models.BuildingBlock.getBuildingBlocks();
ViewBag.BuildingBlockFields = Models.BuildingBlockField.getBuildingBlockFields();
return View();
}
[HttpPost]
public ActionResult generateWordContract(ContractInformationsModel contractInformations)
{
return View("Index");
}
}
I expect to find in my controller object contractInformations to find a list of buildingBlockFields with all the information and not only the value.
Thank you
Edit :
This seems to work but i have to do it for every property and hide then. Is there any other solution ?
#for (var i = 0; i < buildingBlockFields.Count(); i++){
<div class="form-group">
#Html.HiddenFor(model => model.buildingBlockFields[i].BuildingBlockFieldID, new { Value = buildingBlockFields[i].BuildingBlockFieldID })
#Html.HiddenFor(model => model.buildingBlockFields[i].FieldTitle, new { Value = buildingBlockFields[i].FieldTitle })
#Html.HiddenFor(model => model.buildingBlockFields[i].Type, new { Value = buildingBlockFields[i].Type })
#Html.DropDownListFor(model => model.buildingBlockFields[0].Values, buildingBlockFields[i].OptionDetails, "Select Contract", new { #class = "selectpicker", multiple = "multiple" })
</div>
}
Since you are passing ContractInformationsModel model to your view, which has a list of type BuildingBlockField, your html should contain the building block field ID and a "counter" that can identify indexes in that list.
#{
// declare counter
int i = 0
}
#foreach (BuildingBlockField buildingBlockField in buildingBlockFields)
{
<div class="form-group">
<label for="companyName">#buildingBlockField.FieldTitle</label>
#Html.HiddenFor(model=>model.buildingBlockFields[i].BuildingBlockFieldID)
#Html.TextBoxFor(model => model.buildingBlockFields[i].FieldTitle, new { #class = "form-control", Value = #buildingBlockField.FieldTitle })
#Html.DropDownListFor(model => model.buildingBlockFields[i].Values, buildingBlockField.OptionDetails, "Select Contract", new { #class = "selectpicker", multiple = "multiple" })
</div>
#i++
}

Asp.net MVC multiple select for List property

I'm fairly new to ASP.Net MVC so forgive me for anything that should just be obvious.
I have an object that contains a property that is a list. I only don't know how I should implement this in the create.
this is the object:
public class TeamMember
{
public int TeamMemberId { get; set; }
public string FristName { get; set; }
public string LastName { get; set; }
public DateTime BirthDate { get; set; }
public string Biographie { get; set; }
public virtual Image Image { get; set; }
public virtual List<DanGrade> DanGrades { get; set; }
}
In the create view I want to be able to select multiple Dangrades.
I tried to modify an editor Template for it that looks like this:
#using BudoschoolTonNeuhaus.Models
#model BudoschoolTonNeuhaus.Models.TeamMember
#{
var db = new ApplicationDbContext();
var danGrades = db.DanGrades.ToList();
}
<select multiple name="#ViewData.TemplateInfo.HtmlFieldPrefix" class="dropdown">
#foreach (var dan in danGrades)
{
<option value="#">
#dan.DanGradeId: #dan.BudoSport, #dan.Grade
</option>
}
</select>
but this does not give the result that I thought it would, its just showing mutiple dangrade labels in the create view that you can see here:
#model BudoschoolTonNeuhaus.Models.TeamMember
#{
ViewBag.Title = "Create";
Layout = "~/Views/Shared/_Admin_Layout.cshtml";
}
<div class="wrapper">
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>TeamMember</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.FristName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.FristName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.FristName, "", new { #class = "text-danger" })
</div>
</div>
.... // controls for other properties of model
<div class="form-group">
#Html.LabelFor(model => model.DanGrades, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.DanGrades, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.DanGrades, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Image, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<input type="file" id="Image" name="Image" hidden />
</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>
</div>
current HTML output:
Thanks for you help in advance!
To create a <select multiple> you use the ListBoxFor() method in your view.
But your model needs two properties to generate a listbox, a IEnumerable<int> to bind the selected values to (assumes the ID proeprty of DanGrade is typeof int), and an IEnumerable<SelectListItem> to display the <option> elements.
You editing data, so always start with a view model
public class TeamMemberVM
{
public int? TeamMemberId { get; set; }
....
[Display(Name = "DanGrades")]
public IEnumerable<int> SelectedDanGrades { get; set; }
public IEnumerable<SelectListItem> DanGradesList { get; set; }
}
and your view will be
#model yourAssembly.TeamMemberVM
....
#Html.ListBoxFor(m => m.SelectedDanGrades, Model.DanGradesList, new { #class="dropdown" })
and your controller methods will be
public ActionResult Create()
{
TeamMemberVM model = new TeamMemberVM();
ConfigureViewModel(model);
// For an Edit method, your would set the existing selected items here
model.SelectedDanGrades = ...
return View(model);
}
public ActionResult Create(TeamMemberVM model)
{
if (!ModelState.IsValid)
{
ConfigureViewModel(model); // repopulate the SelectList
return View(model);
}
// model.SelectedDanGrades contains the ID's of the selected options
// Initialize an instance of your data model, set its properties based on the view model
// Save and redirect
}
private void ConfigureViewModel(TeamMemberVM model)
{
IEnumerable<DanGrade> danGrades = db.DanGrades();
model.DanGradesList = danGrades.Select(x => new SelectListItem
{
Value = x.DanGradeId.ToString(),
Text = x.??? // the name of the property you want to use for the display text
});
}
Note also that your view has a file input so your view model needs a HttpPostedFileBase property to bind the file to
public HttpPostedFileBase Image { get; set; }
and in the view
#Html.TextBoxFor(m => m.Image, { new type ="file" })
Shouldn't your model be like that ?
[UIHint("NameOfTheEditorTemplate")]
public virtual List<DanGrade> DanGrades { get; set; }
Be sure to put the EditorTemplate under one of these two paths
~/Views/Shared/EditorTemplates
~/Views/Controller_Name/EditorTemplates
As explained in this post
So you are trying to save a list of custom objects inside your object. First of all, know that if you try to save teammember to a database your list of objects will not save. I've experienced this same issue and its needs some special configuring to get just that to work.
Second you can't select custom objects from a < select >. Select returns string[] to your controller. So objects, no. You can't return complex items like that using select directly.
What you can do is return a string[] and use the individual strings (maybe it contains name, maybe it contains id?) and then use that array to pull each object to your teammember object in the controller from the dangrade db context (I'm assuming that is where they are stored).
So for example if you Go back to your controller and add (string[] dangrades) to your parameters. Your parameters now looks something like this (string[] dangrades, Bind[blahblah] ... teammember).
Now after referencing the other database you can do as follows
teammember.Dangrades = new list<Dangrade>();
foreach(string item in dangrades)
{
var dangradeselected = from x in db.dangrades where x.name = item select x;
var dangradefromlinq = dangradeselected.tolist();
teammember.Dangrades.Add(dangradefromlinq[0]);
}
If you had previously stored dangrades in some other format (ie not a database) then you will have to append your code, or ask specifically with that for a better answer.
Also don't forget to give your select and id= (lookup html attributes) so that the controller can recognize it.
You can probably make this (pseudo)code a little neater. Also don't forget about possible null values.
If you want to save a list of items for each teamember you can also look into having 2 databases. I'm not sure if this is recommended. But you can have one for teammembers, and one for dangrades. In the case of dangrades you would add an additional property called grouping id that would match the id of your teammember. So when you pull up your teammember you could also pull up all related dawngrades that match its database id.
That's everything I can think of. If you find a simpler solution by all means go with that.

DropDownList doesn't want to be set

My dropDown list doesn't want to have default value!
<div class="form-group">
#Html.LabelFor(model => model.unit, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(x => x.unit.id, selectUnit)
#Html.ValidationMessageFor(model => model.unit.id, "", new { #class = "text-danger" })
</div>
</div>
Show me the right list but none is selected.
I get my SelectList by using ViewBag:
#{
IEnumerable<SelectListItem> selectUnit = ViewBag.Unit;
}
When I breakpoint the cshtml, Model.unit.id is 4 and selectUnit have one item with 4 as value.
When I do
#selectUnit.Where(x => x.Value == Model.unit.id.ToString()).First().Text
it selects the right text value!
Lats think: this is my Unit model:
public class Unit
{
public int id { get; set; }
public string name { get; set; }
public string description { get; set; }
public IList<Unit> children { get; set; }
}
Thanks in advance folks, I'm becoming crasy
EDIT:
public class ModelPassedTroughTheView
{
...
public Unit unit { get; set; }
}
EDIT 2: Full code:
Edit page:
#model BE.DealerGroupSAP
#{
ViewBag.Title = Resources.Admin.DealerGroup_Edit;
IEnumerable<SelectListItem> selectUnit = ViewBag.Unit;
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>#ViewBag.Title</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.id)
<div class="form-group">
#Html.LabelFor(model => model.unit, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(x => x.unit.id, selectUnit)
#Html.ValidationMessageFor(model => model.unit.id, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="#Resources.Global.Save_Edits" class="btn btn-default" />
</div>
</div>
</div>
}
Model passer trough view:
public class DealerGroupSAP
{
public int id { get; set; }
public Unit unit { get; set; }
}
Unit object:
public class Unit
{
public int id { get; set; }
public string name { get; set; }
public string description { get; set; }
public IList<Unit> children { get; set; }
}
Controller's content:
ViewBag.Unit = GetUnits();
return View(BL.AnomalyBL.GetAllSAPResponsible(id));
The issue is that your model has a property named unit and your also passing the SelectList view a ViewBag property named Unit (the model binding features of MVC are case insensitive.
Change the name of the ViewBag property to (say)
ViewBag.UnitList = GetUnits();
and in the view
#{ IEnumerable<SelectListItem> selectUnit = ViewBag.UnitList }
and the correct option will be selected.
To explain what is happening internally:
The DropDownListFor() method determines the defaultValue (selected item) by first checking values in ModelState (which in your case do not exist), then checking ViewData. Because ViewData contains a key/value pair for Unit, which is IEnumerable<SelectListItem> and does not contain a property id, the defaultValue is nulland the method uses the IEnumerable<SelectListItem> you passed to the view to build the <option> elements, none of which have a Selected = true value, so the first option is selected because something has to be.
Changing the ViewBag property to to (say) UnitList means the method does not find a matching key for unit in ViewData and now inspects the model for unit.id, which exists, and sets defaultValue = 4. Because defaultValue is not null, a new IEnumerable<SelectListItem> is generated internally, and the corresponding SelectListItem has its Selected property set to true.
To understand how this all works in detail, you can inspect the source code for SelectExtensions - in particular the private static MvcHtmlString SelectInternal() method.
As a final note, this is just one more reason why you should always use a view model.

There is no ViewData item of type 'IEnumerable<SelectListItem>' that has the key 'PestType'

I am learning MVC and Entity Framework, so pleas bear my questions... I am trying to create a drop down menu for a property (Enum type) for my model class SinglePest
Here is the model:
public class SinglePest
{
public int SinglePestId { get; set; }
public PestType PestType { get; set; } // here is my problem
public string Alias { get; set; }
public string TechName { get; set; }
public string Markings { get; set; }
public string SerialNumber { get; set; }
public SourceType SourceType { get; set; }
//virtual property
public Source Source { get; set; }
}
Here is PestType:
public enum PestType
{
Dog,
Cat,
Fox,
Rabbit,
Rat
}
This is the controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create (//(SinglePest singlepest)
[Bind(Include= "Alias, TechName, SerialNumber, PestType, Markings")] SinglePest singlepest)
{
try
{
if (ModelState.IsValid)
{
db.SinglePests.Add(singlepest);
db.SaveChanges();
return RedirectToAction("Index");
}
}
catch (DataException /* dex */)
{
ModelState.AddModelError("", "Unable to save changes, try again, if problem persits contact your administrator");
}
//ViewBag.SerialNumber = new SelectList(db.Sources, "SerialNumber", "SerialNumber", singlepest.SerialNumber);
return View(singlepest);
}
And here is the View where I get the error (There is no ViewData item of type 'IEnumerable' that has the key 'PestType'.) :
<div class="editor-label">
#Html.LabelFor(model => model.PestType, "Pest Type")
</div>
<div class="editor-field">
#Html.DropDownList("PestType", String.Empty) // here is the error
#Html.ValidationMessageFor(model => model.PestType.ToString())
</div>
Now I have seen some posts about displaying enum, but I can't figure out a solution for my problem. Could please someone give me some piece of advice on how to fix it?
Thank you for your time!
You have #Html.DropDownList("PestType", String.Empty), but the second param needs to be an IEnumerable<T>. You will need the list of your pest in the model, and then use model.Pests for example where Pets is an IEnumerable.
EDIT: Based on comment...
But I want to display just the various types of pest (Dog, Cat, etc)
not all the pests that are in my database
OK, are these categorised, could you write something like (hand written so check syntax)..
var pests = (from _context.Pests.Where(p => p.CategoryId == 1) select p.PestName).ToList();
If you need to get a IEnumerable for the enum (since I'm not sure what the DB looks like), you can use...
Enum.GetValues(typeof(PestType))
.OfType<PestType>()
.Where(p => p == ??);
Currently your model only contains a place to store the value and not a Array/IEnumerable to populate the drop down from.
First add an IEnumerable to your model:
public class SinglePest
{
public int SinglePestId { get; set; }
public IEnumerable<PestType> Pests { get; set; }
public PestType PestType { get; set; }
public string Alias { get; set; }
public string TechName { get; set; }
public string Markings { get; set; }
public string SerialNumber { get; set; }
public SourceType SourceType { get; set; }
//virtual property
public Source Source { get; set; }
}
And in your controller:
public ActionResult Create()
{
var model = new SinglePest();
model.Pests = Enum.GetValues(typeof(PestType)).Cast<PestType>()
return View(model);
}
And your view:
#Html.DropDownListFor(m => m.PestType, Model.Pests);
Sorry if theres any errors I've written this from memory...
I found this post on DropDownListFor enums
It seems it is solving my problem.
so I have just changed the view with this new piece of code:
<div class="editor-label">
#Html.LabelFor(model => model.PestType, "Pest Type")
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.PestType, new SelectList(Enum.GetValues(typeof( MvcTrackingSystem.Enums.PestType))))
#Html.ValidationMessageFor(model => model.PestType)
</div>
Now it looks like it is working
On the difference between DropDownList and DropDownListFor I have found useful infromation on this post
You can fix the original problem quite simply with this in your GET Create method:
ViewBag.PestType = new SelectList(Enum.GetValues(typeof(PestType)).OfType<PestType>());
and this in your POST Create method (where validation fails and it returns the view):
ViewBag.PestType = new SelectList(Enum.GetValues(typeof(PestType)).OfType<PestType>(),
singlepest.PestType);
if you want to keep it strongly typed in the view use:
#Html.DropDownListFor(model => model.PestType, ViewBag.PestType as SelectList)
If you don't mind the weak typed version use the simpler:
#Html.DropDownList("PestType")
In either case I suggest you create all lists in the controller and not in the view.
Explanation:
Basically DropDownlist will search the ViewData (i.e. your ViewBag settings) for a list of options for the member of the same name (if a list is not explicitly provided).
I mocked up the whole project (MVC5/VS2013) and more of the code is below for your reference.
Controller:
using PestTypeTest.Models;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace PestTypeTest.Controllers
{
public class PestTypeController : Controller
{
//
// GET: /PestType/
public ActionResult Index()
{
return RedirectToAction("Create");
}
public ActionResult Create()
{
ViewBag.PestType = new SelectList(Enum.GetValues(typeof(PestType)).OfType<PestType>());
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(//(SinglePest singlepest)
[Bind(Include = "Alias, TechName, SerialNumber, PestType, Markings")] SinglePest singlepest)
{
try
{
if (ModelState.IsValid)
{
//db.SinglePests.Add(singlepest);
//db.SaveChanges();
return RedirectToAction("Index");
}
}
catch (DataException /* dex */)
{
ModelState.AddModelError("", "Unable to save changes, try again, if problem persits contact your administrator");
}
//ViewBag.SerialNumber = new SelectList(db.Sources, "SerialNumber", "SerialNumber", singlepest.SerialNumber);
ViewBag.PestType = new SelectList(Enum.GetValues(typeof(PestType)).OfType<PestType>(), singlepest.PestType);
return View(singlepest);
}
}
}
Views\PestType\Create.cshtml
#model PestTypeTest.Models.SinglePest
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>SinglePest</h4>
<hr />
#Html.ValidationSummary(true)
<div class="form-group">
#Html.LabelFor(model => model.PestType, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("PestType")
#Html.ValidationMessageFor(model => model.PestType)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Alias, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Alias)
#Html.ValidationMessageFor(model => model.Alias)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.TechName, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.TechName)
#Html.ValidationMessageFor(model => model.TechName)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Markings, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Markings)
#Html.ValidationMessageFor(model => model.Markings)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.SerialNumber, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.SerialNumber)
#Html.ValidationMessageFor(model => model.SerialNumber)
</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")
}

No parameterless constructor defined for this object- Unable to Solve

I am new to C# and MVC so please forgive any naiveness/stupidity. I have seen multiple posts dealing with the error I have and I have tried all the solutions but they dont seem to work in my case. Please guide me to the right way to solve this problem.
Background:
I have 2 models tblPrograms and tblReports.
I am trying to build a simple form where the user can create a new tblReport. Part of the process of creating a new tblReport is to select one tblProgram from a list.
Code Overview:
Controller:
// GET: /Reports/
public ViewResult New()
{
ProgramListModel progList = new ProgramListModel();
tblReports2 report = new tblReports2();
ExistingReportViewModel ervm = new ExistingReportViewModel(report,progList);
return View(ervm);
}
[HttpPost]
public ActionResult GenerateSQL(ExistingReportViewModel evrm)
{
evrm.Report.ProgramName = evrm.progList.selectedProgram;
return View(evrm);
}
View Model:
public class ExistingReportViewModel
{
public tblReports2 Report { get; set; }
public ProgramListModel progList { get; set; }
//public ExistingReportViewModel() { }
public ExistingReportViewModel(tblReports2 report, ProgramListModel progList)
{
this.Report = report;
this.progList = progList;
}
}
Models:
public class tblReports2
{
[Key]
public string ProgramName { get; set; }
public string ReportDesc { get; set; }
....
}
public class ProgramListModel
{
public SelectList progList;
public string selectedProgram;
public ProgramListModel() {
ReportHelper helper = new ReportHelper();
this.progList = helper.getProgramList();
}
}
Helper Method:
public SelectList getProgramList()
{
var programs = from p in this.DB.tblProgramsSet
select p;
programs = programs.Where(s => s.ProgramType.Equals("RPT"));
SelectList list = new SelectList(programs, "ProgramName", "ProgramDescription");
return list;
}
View:
#model ReportsSetup.Models.ExistingReportViewModel
#{
ViewBag.Title = "New";
}
New
#using (Html.BeginForm("GenerateSQL", "Reports"))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>ExistingReportViewModel</legend>
#Html.DropDownListFor(model=>model.progList.selectedProgram, Model.progList.progList)
<div class="editor-label">
#Html.LabelFor(model => model.Report.ReportDesc)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Report.ReportDesc)
#Html.ValidationMessageFor(model => model.Report.ReportDesc)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Report.Formerly)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Report.Formerly)
#Html.ValidationMessageFor(model => model.Report.Formerly)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Report.ExtendedDesc)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Report.ExtendedDesc)
#Html.ValidationMessageFor(model => model.Report.ExtendedDesc)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Report.ReportCode)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Report.ReportCode)
#Html.ValidationMessageFor(model => model.Report.ReportCode)
</div>
...
<input type="submit" value="Create" />
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Error:
![Exception Details: System.MissingMethodException: No parameterless constructor defined for this object.][1]
http://i.stack.imgur.com/GHJpG.jpg
Attempted Solutions:
Refactored the code to use IEnumerable <tblPrograms> instead of
SelectList in ProgramListModel and converting to
IEnumerable in View using a function similar to
sample below:
public static IEnumerable<SelectListItem> ToSelectListItems(
this IEnumerable<Album> albums, int selectedId)
{
return
albums.OrderBy(album => album.Name)
.Select(album =>
new SelectListItem
{
Selected = (album.ID == selectedId),
Text = album.Name,
Value = album.ID.ToString()
});
}
Refactored the code to use IEnumerable<SelectListItem> instead of
SelectList in ProgramListModel
Many Thanks in Advance!!
No, you can't do that:
public ActionResult GenerateSQL(ExistingReportViewModel evrm)
if your ExistingReportViewModel doesn't have a parameterless constructor. The default model binder doesn't know how to instantiate this class if it doesn't have a default constructor.
So you have 2 possibilities:
Define a parametrless constructor to this class:
public class ExistingReportViewModel
{
public ExistingReportViewModel()
{ }
... some other stuff of course
}
Write a custom model binder
Definitely go for 1. unless you need something very special. If you need something very special then please define it precisely, so that we can help you with 2.

Categories

Resources