Passing a List from View to Controller in Model - c#

I am trying to pass a complex data structure from Controller to View and Back to Controller which contains Lists. I can see the list items in View. I want to edit those and send it back to the controller. I am able to edit some properties but for lists, I am getting null value in the controller.
Here is an example (simulation) of what I am trying to achieve:
Consider Model -
using System.Collections.Generic;
namespace WebApplication1.Models
{
public class StudentViewModel
{
public string StudentId { get; set; }
public string FeedBack { get; set; }
public List<ScoreCard> ScoreCards;
}
public class ScoreCard
{
public string Subject { get; set; }
public string Marks { get; set; }
}
}
Controller -
public class StudentController : Controller
{
public ActionResult Index()
{
var model = new StudentViewModel();
model.StudentId = "Some Student";
model.ScoreCards = new List<ScoreCard>
{
new ScoreCard()
{
Marks = "0",
Subject = "English"
},
new ScoreCard()
{
Marks = "0",
Subject = "Maths"
}
};
return View("View", model);
}
public ActionResult SubmitScore(StudentViewModel model)
{
/* Some Code */
}
}
View -
#model WebApplication1.Models.StudentViewModel
#{
ViewBag.Title = "Title";
}
#using (Html.BeginForm("SubmitScore", "Student", FormMethod.Post))
{
#Html.DisplayName(#Model.StudentId)<br />
<label>Comment:</label><br/>
#Html.EditorFor(m => m.FeedBack, new { htmlAttributes = new { #type = "text", id = #Model.FeedBack} })<br />
for (var i = 0; i < #Model.ScoreCards.Count; i++)
{
#Html.DisplayName(#Model.ScoreCards[i].Subject) <br/>
#Html.EditorFor(m => m.ScoreCards[i].Marks, new { htmlAttributes = new { #type = "number", #min = 0, id = #Model.ScoreCards[i].Marks} })<br />
}
<input class="btn btn-primary" type="submit" value="Submit" />
}
When I run the application -
When I click submit, I am able to see model.FeedBack but the list is set to null.
Something similar is achieved in this question & it's answer; I am not sure what exactly I am missing here.

you send no input for the Subject . You'll need a hidden input for that along with any other value you want returned to the controller when the form is posted. Also a simple text box should work for the marks.
#using (Html.BeginForm("SubmitScore", "Student", FormMethod.Post))
{
#Html.DisplayName(#Model.StudentId)<br />
#Html.HiddenFor(m => m.StudentId)
<label>Comment:</label><br/>
#Html.EditorFor(m => m.FeedBack, new { htmlAttributes = new { #type = "text", id = #Model.FeedBack} })<br />
for (var i = 0; i < #Model.ScoreCards.Count; i++) {
#Html.HiddenFor(m => m.ScoreCards[i].Subject)
#Html.DisplayName(#Model.ScoreCards[i].Subject) <br/>
#Html.TextBoxFor(m => m.ScoreCards[i].Marks, new { htmlAttributes = new { #type = "number", #min = 0} })<br />
}
<input class="btn btn-primary" type="submit" value="Submit" />
}
Finally you need to use properties in order for the model binding to work. You currently have ScoreCards as a field so update it to a property.
public class StudentViewModel {
public string StudentId { get; set; }
public string FeedBack { get; set; }
public List<ScoreCard> ScoreCards { get; set; }
}

Related

MVC Dropdownlistfor returns null selected value

Similar to this question and a lot of others, I'm not able to retrieve the selected value from my #Html.Dropdownlistfor. But I think I avoided usual errors so I'm not sure what I'm doing wrong. Here is my Controller :
public class AdministrationController : Controller
{
public ActionResult ParamStatique()
{
ParamStatiqueViewModels psvm = new ParamStatiqueViewModels()
{
a_NEquipe = "1"
};
using (Dal dal = new Dal())
{
psvm.EquipesTravaux = dal.GetEquipesTravaux();
}
return View(psvm);
}
[HttpPost]
public ActionResult ParamStatique(ParamStatiqueViewModels psvm)
{
Debug.WriteLine("NEquipe : " + psvm.a_NEquipe);
using (Dal dal = new Dal())
{
psvm.EquipesTravaux = dal.GetEquipesTravaux();
}
return View(psvm);
}
}
My ModelView looks like this :
public class ParamStatiqueViewModels
{
public List<EQUIPE_TRAVAUX> EquipesTravaux { get; set; }
[Display(Name = "N° Équipe")]
public string a_NEquipe { get; set; }
}
Here is my View :
#using (Html.BeginForm("ParamStatique", "Administration", null, FormMethod.Post, new { id = "modalform", role = "form" }))
{
<div class="modal-body" id="modal-body">
<div class="form-group" id="aNEquipe">
#Html.LabelFor(m => m.a_NEquipe, new { #class = "col-form-label" })
#Html.DropDownListFor(m => m.a_NEquipe, new SelectList(Model.EquipesTravaux, "TRAV_SEQ", "TRAV_CODE"), new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.a_NEquipe, "", new { #class = "text-danger" })
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">Enregistrer</button>
</div>
}
And so my EQUIPE_TRAVAUX class, generated from EF6 :
public partial class EQUIPE_TRAVAUX
{
public short TRAV_SEQ { get; set; }
public string TRAV_CODE { get; set; }
}
The Dropdown is correctly populated and I can select the option I want, but when I click the "Enregistrer" submit button it returns a null a_NEquipe field in my model, even if I correctly specify it in the Html Helper :
#Html.DropDownListFor(m => m.a_NEquipe, new SelectList(Model.EquipesTravaux, "TRAV_SEQ", "TRAV_CODE"), new { #class = "form-control" })
The result of the Debug.WriteLine("NEquipe : " + psvm.a_NEquipe); is always null like this :
NEquipe :
And if I add the [Required] tag to my model, the client-side validation block the form postback call.
How can I retrieve the selected value in the expected variable a_NEquipe ?
Try to specify the Text and Value fields like this:
#Html.DropDownListFor(model => model.a_NEquipe, Model.EquipesTravaux.Select(x => new SelectListItem { Text = x.TRAV_SEQ, Value = x.TRAV_CODE }), new { #class = "form-control"})

ViewModel enumeration is empty on HttpPost

I'm working in ASP.Net, and have an enumeration of viewmodels I want to display in a page. They contain a dropdown, and when said dropdown is updated, I have to take that enumeration of viewmodels, change the hirearchy inside it, and post it back with the changes. However, when I make my post, instead of sending the contained viewmodels, it sends an empty enumeration.
So far, I've tried switching my "foreach" in my view for a "for". I've also tried changing some names, seeing if the model binder was being the issue, but it doesn't seem so.
Here is the code for, respectively, the view, the viewmodel and the controller.
View
#using (Html.BeginForm())
{
#Html.AntiForgeryToken();
for (int i = 0; i < Model.Count(); i++)
{
#Html.DropDownListFor(model => model.ElementAt(i).PickedNormId, Model.ElementAt(i).PickableNorms, UiText.Prosyn.N_A, htmlAttributes: new { #class = "form-control dropdown_search", #name="NormPicker"#*, #onchange = "this.form.submit();"*# })
}
<div>
<input name="selectedNormList" type="submit">
</div>
}
ViewModel
public class NormPickerViewModel : ViewModel
{
public SelectList PickableNorms { get; set; }
public int PickedNormId { get; set; }
}
Controller
public virtual ActionResult Index()
{
List<NormPickerViewModel> viewModels = new List<NormPickerViewModel>();
viewModels.Add(new NormPickerViewModel()
{
PickableNorms = new SelectList(
_normRepository.GetAll().Where(x => x.Depth == 1).ToList(),
"Id",
"Description"
)
});
return View(viewModels);
}
[HttpPost]
public virtual ActionResult Index(IEnumerable<NormPickerViewModel> selectedNormList)
{
// selectedNormList's count is always zero.
}
Normally, I'd expect to find the dropdowns I passed in the "Get" with their updated selected values, but what I'm getting is an empty enumeration.
I tried reproducing your source code, you need update 2 items and it worked:
Change from IEnumerable<NormPickerViewModel> selectedNormList to IEnumerable<NormPickerViewModel> normPickerViewModel in controller.
Add name for select tag (DropDownListFor) Name = "normPickerViewModel[" + i + "].PickedNormId" in cshtml file.
Here is Controller code
[HttpGet]
public virtual ActionResult Index()
{
List<NormPickerViewModel> viewModels = new List<NormPickerViewModel>();
viewModels.Add(new NormPickerViewModel()
{
PickableNorms = new SelectList(
new List<dynamic> { new { Id = 1, Description = "Test1" }, new { Id = 2, Description = "Test2" } },
"Id",
"Description"
)
});
viewModels.Add(new NormPickerViewModel()
{
PickableNorms = new SelectList(
new List<dynamic> { new { Id = 3, Description = "Test3" }, new { Id = 4, Description = "Test4" } },
"Id",
"Description"
)
});
return View(viewModels);
}
[HttpPost]
public virtual ActionResult Index(IEnumerable<NormPickerViewModel> normPickerViewModel)
{
// selectedNormList's count is always zero.
return null;
}
and cshtml code
#using (Html.BeginForm())
{
#Html.AntiForgeryToken();
for (int i = 0; i < Model.Count(); i++)
{
#Html.DropDownListFor(model => model.ElementAt(i).PickedNormId, Model.ElementAt(i).PickableNorms, "NA", htmlAttributes: new
{
#class = "form-control dropdown_search",
Name = "normPickerViewModel[" + i + "].PickedNormId"#*, #onchange = "this.form.submit();"*#
})
}
<div>
<input name="selectedNormList" type="submit">
</div>
}

After Implementing Validations in c#, I face problems in updating records

I'm working on a project in Asp.net c# MVC and I want to implement validations on a model using Data Annotations as follows:
public class MainRepository
{
public int Id { get; set; }
[Required]
public int Category_Id { get; set; }
[Required]
public int Department_Id { get; set; }
[Required]
public DateTime Start_Date { get; set; }
}
I have a controller as PM controller with the following Register Method.
public ActionResult Register()
{
var event_category = _context.Event_Categories.ToList();
var departments = _context.dept.ToList();
var vm = new InsertEdit_ViewModel
{
evt_catgrss = event_category,
depts = departmetns
};
return View(vm);
}
Here is the InsertEdit view Model:
public class InsertEdit_ViewModel
{
public MainRepository Main_RP { get; set; }
public List<Event_Categories> evt_catgrss { get; set; }
public List<Departments> depts { get; set; }
}
public InsertEdit_ViewModel()
{
Main_RP = new MainRepository();
evt_catgrss = new List<Event_Categories>();
depts = new List<Departments>();
}
}
And this is the view for the Register Method:
#model Project.ViewModel.InsertEdit_ViewModel
#using (Html.BeginForm("Store", "PM", FormMethod.Post, new { enctype = "multipart/form-data"}))
{
<div class="form-group">
<label>Event Category</label><br/>
#Html.DropDownListFor(a => a.Main_RP.Category_Id, new SelectList(Model.evt_catgrss, "Id", "type_of_event"), "Select a category", new { #class = "form-control btn-group dropdown-menu" })
#Html.ValidationMessageFor(a=> a.Main_RP.Category_Id)
</div>
<div class="form-group">
<label>Department</label>
#Html.DropDownListFor(a => a.Main_RP.Department_Id, new SelectList(Model.depts, "Id", "DepartmentName"), "Select Employee Department", new { #class = "form-control btn-group dropdown-menu" })
#Html.ValidationMessageFor(a=> a.Main_RP.Department_Id)
</div>
<div class="form-group">
<label>Start Date</label>
#Html.TextBoxFor(a => a.Main_RP.Start_Date, "Select Estimated Date of Start", new { #class = "form-control", #readonly = "readonly", #style = "cursor :default; background-color:#d4d4d4; font-size:11px;" })
#Html.ValidationMessageFor(a=> a.Main_RP.Start_Date)
</div>
#Html.AntiForgeryToken();
<button class="btn btn-primary">Register</button>
}
And finally this is the Store Method within the PM controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Store(InsertEdit_ViewModel pmI)
{
if (!ModelState.IsValid)
{
var event_category = _context.Event_Categories.ToList();
var departments = _context.dept.ToList();
var vm = new InsertEdit_ViewModel
{
evt_catgrss = event_category,
depts = departmetns
};
return View("Register",vm);
}
_context.main_repz.Add(pmI.Main_RP);
_context.SaveChanges();
return RedirectToAction("Index", "SystemAdmin");
}
Now till this portion everything is working fine including the validations. But I'm facing issues whenever i want to change the date of an event with another method as follows:
This is the Change Method within PM Controller:
public ActionResult Change(int? Id)
{
var EventDetails = _context.main_repz.Include(a => a.Event_Categories).SingleOrDefault(a => a.Id == Id);
var vm = new ChangeVM()
{
Main_RP = EventDetails
};
return View(vm);
}
This is the ChangeVM (ViewModel)
public class ChangeVM
{
public MainRepository Main_RP { get; set; }
public ChangeVM()
{
Main_RP = new MainRepository();
}
}
This is the view for Change Method
#model FEFA_MIS.ViewModel.ChangeVM
#using (Html.BeginForm("ChangeDate", "PM", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<div class="form-group">
<label>Select New Date</label>
#Html.TextBoxFor(a => a.Main_RP.Start_Date, "{0:}", new { #class = "form-control"})
#Html.ValidationMessageFor(a=> a.Main_RP.Start_Date)
</div>
#Html.AntiForgeryToken();
#Html.HiddenFor(a => a.Main_RP.Id);
<button class="btn btn-primary">Request</button>
}
And Finally this is the ChangeDate Method within PM controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ChangeDate(ChangeVM ap)
{
if (!ModelState.IsValid)
{
return View("Change",ap);
}
var item = _context.main_repz.Single(a => a.Id == ap.Main_RP.Id);
item.Start_Date = ap.Main_RP.Start_Date;
_context.SaveChanges();
return RedirectToAction("Success", "PM");
}
Now this time it is not working properly,
Means, If I don’t select the new date It gives the validation message, which is perfectly fine.
But when I select the new date It does not proceed as I think it is expecting the Category_Id and Department_Id too, but they are no longer part of ChangeDate Method, they were part of Store Method used for Registration.
If i'm not mistaken, I think all of the three [Required] fields in the model, come under one ModelState, but anyhow I'm stuck here...
what can be the solution? Can the View Models access only specific attributes instead of calling the whole class (as in my case)?
I would not use the MainRepository in your ChangeVM. This includes all properties. Instead, limit your ViewModels to only those fields, that you are actually processing in that view. So, just add Id and Start_Date to the ChangeVM, and you should be good.
When you try to do update operation you must provide required properties value.This is mandatory.
Thanks

MVC ViewModel not posting back

I have seen lots of examples on this, but cannot get any working. I have built this example to prove/disprove passing back of the view model SomeDataViewModel.
I am trying to post back the dropdownlist data. Everything works ok, but the OtherData property on TestViewModel never returns the collect that was passed in.
Have tried adding:
#Html.HiddenFor(m => Model.OtherData)
but again this just produces the following error;
The parameter conversion from type 'System.String' to type 'SomeDataViewModel' failed because no type converter can convert between these types
The Code:
ViewModels
TestViewmodel
public class TestViewModel
{
public TestViewModel()
{
OtherData = new List<SomeDataViewModel>();
}
public int Id { get; set; }
public String Name { get; set; }
public DateTime DoB { get; set; }
public int SelectedOtherData { get; set; }
public List<SomeDataViewModel> OtherData { get; set; }
public IEnumerable<SelectListItem> TolistData()
{
IEnumerable<SelectListItem> ret = OtherData.Select(i => new SelectListItem() { Text = i.Text, Value=i.Value });
return ret;
}
}
SomeDataViewmodel
public class SomeDataViewModel
{
public string Value { get; set; }
public string Text { get; set; }
}
View
#model TestViewModel
#{
ViewBag.Title = "Home Page";
}
#using (Html.BeginForm("Index","Home"))
{
<div class="row">
<div class="col-md-12">
<br />
#Html.EditorFor(m => Model.Id)
<br />
#Html.EditorFor(m => Model.Name)
<br />
#Html.EditorFor(m => Model.DoB)
<br/>
#Html.DropDownListFor(m => Model.SelectedOtherData, Model.TolistData(), new { id = "OtherData" })
<p><a class="btn btn-default" href="http://go.microsoft.com/fwlink/?LinkId=301865">Learn more »</a></p>
</div>
</div>
<button id="dosomething" formmethod="post">Post</button>
}
Controller
public ActionResult Index()
{
var model = new TestViewModel() {
Id = 99,
Name = "Billy",
DoB = DateTime.Now
};
model.OtherData.Add(
new SomeDataViewModel { Text = "Bob", Value = "1" });
model.OtherData.Add(
new SomeDataViewModel { Text = "Sally", Value = "2" });
return View(model);
}
[HttpPost]
public ActionResult Index(TestViewModel retModel)
{
if (ModelState.IsValid)
{
if (retModel.OtherData.Count() == 0)
{
var dud = true;
}
}
return View(retModel);
}
You can't render hidden inputs for complex data with #Html.HiddenFor helper.
You should use it only with simple types. Becouse you got array you should write something like this:
#for(int i = 0; i < Model.OtherData.Count(); i++)
{
#Html.HiddenFor(m => Model.OtherData[i].Text)
#Html.HiddenFor(m => Model.OtherData[i].Value)
//... other fields.
#Html.HiddenFor(m => Model.OtherData[i].OtherProperty)
}
Use for loop instead of foreach becouse you should same your mappings for right binding on form POST.
Certainly there is a type conversion error. your SelectedOtherData is type of int while selectlistitem value is type of string

Passing a dictionary to a controller

I have a ViewModel that I'm trying to pass to my controller which contains a dictionary. I'm able to get everything to show up correctly in the view, however when I got to post to my controller I never get the the StudentKeys dictionary to go through, however all other information goes through fine.
View
#using (Html.BeginForm("BulkEditStudentRecordsAdultEd", "Order", FormMethod.Post, new { #id = "EditStudentAdultEd", #class = "form-horizontal" }))
{
#Html.HiddenFor(m => m.AdultEdAgencyCd)
foreach (var item in Model.StudentKeys)
{
#Html.HiddenFor(m => item.Key)
#Html.HiddenFor(m => item.Value)
}
<h1>#Model.StudentKeys.Count</h1>
<div class="row inputRow">
#Html.BootstrapDropDownListFor(m => m.DistrictCd, Model.DistrictCodes, "col-md-5", labelText: "District:", htmlAttributes: new { #id = "DistrictCd" })
#Html.BootstrapDropDownListFor(m => m.SchoolCd, Model.SchoolCodes, "col-md-5", labelText: "School:", htmlAttributes: new { #id = "SchoolCd" })
</div>
<div class="col-md-offset-2">
<button id="SaveStudentBulkEdit" type="submit" class="btn btn-primary btn-sm">Save <span class="badge">0</span></button>
<button id="CancelStudentBulkEdit" type="button" class="btn btn-default btn-sm" data-dismiss="modal">Cancel</button>
</div>
}
Controller
[HttpPost]
[CustomAuthorize(DiplomaRoles.SchoolStaff, DiplomaRoles.StateAdultEdManagement, DiplomaRoles.SchoolAdmin, DiplomaRoles.StateAdmin)]
public ActionResult BulkEditStudentRecordsAdultEd(EditStudentAdultEdViewModel vm) //All other information from my view model gets through
{
var keys = vm.StudentKeys; //Empty
var DistrictCd = vm.DistrictCd; //Ok
var SchoolCd = vm.SchoolCd; //Ok
}
View Model
public class EditStudentAdultEdViewModel
{
public EditStudentAdultEdViewModel()
{
StudentKeys = new Dictionary<string, string>();
}
public string DistrictCd {get; set;}
public string SchoolCd {get; set;}
public string IdentificationNbr{ get; set; }
public int DuplicateNbr { get; set; }
public Dictionary<string, string> StudentKeys { get; set; }
public string AdultEdAgencyCd { get; set; }
}
MVC already puts everything that was submitted into a dictionary for you.
public ActionResult BulkEditStudentRecordsAdultEd(FormCollection vm)
{
// to access an item, do this: vm["inputName"]
}
If you want the default modelbinder to bind for you into a different dictionary, you can do so by having inputs with correct names:
<input type="hidden" name="vm[KeyString]" value="ValueString" />

Categories