Saving "Select Multiple" values in a database - c#

I have from where teachers can select multiple options (each option represents a skill they posses).
The form looks like this
#model Ability.Models.ViewModel
<h2>Add Skill</h2>
<form action="" method="post">
<select multiple name="AddSkillForm">
#foreach (var skill in Model.Skills)
{
<option value="#skill.ID">#skill.SkillName</option>
}
</select>
</form>
As you can see, they can select more then one option.
My database looks like this
Now I was wondering how I can succesfully save the data in my form. I was thinking of using a url that looks like
teacher/addskills/1
Where "1" represents the Teacher_ID and ofcourse the skill_ID's are given though the form.
My question is, how can I pass the multiple values too my controller and save them correctly in my DB?
Looking forward to any help!

As Darin suggested.
The correct way is to use a view model in conjunction with the
Html.ListBoxFor strongly typed helper
I would use a multi select ListBox. The model binder will take care of the rest.
Model view
public class MyModelView
{
public IEnumerable<SelectListItem> Skills{ get; set; }
public string[] SelectedSkills { get; set; } // You could use List<string> instead.
public int TeacherId {get;set;}
}
View
#Html.ListBoxFor(s => s.SelectedSkills ,
new MultiSelectList(Model.Skills, "Value", "Text", Model.SelectedSkills ),
new { #class = "form-control", style = "height:250px; width:100%" })
#Html.HiddenFor(s => s.TeacherId)
Controller
[HttpPost]
public ActionResult Save(MyModelView model)
{
foreach (string skill in model.SelectedSkills){//Selected Skills. Save to database}
}
EDIT
Get skills from database. Based on your database structure you should be able to get all skills per teacher from the bridge table "TeacherSkills". You will need to fill you SelectedSkills property where Skills should have all the available skills.
List<SelectListItem> selectedSkills= service.GetSkillsByTeacherId(teacherId);
if (selectedSkills != null && selectedSkills.Count > 0)
{
model.SelectedSkills = selectedSkills.Select(x => x.Value).ToArray();
}

Related

Entity set to null after selected item from SelectList

When creating Employee entity you are supposed to select MeetingCenterfrom DropDownList. All MeetingCenters show just fine in DropDownList with their Names, but when some of them is selected and Employee is created Meeting Center is null. Im using NoSQL DocumentDB database.
Controller:
[ActionName("Create")]
public async Task<IActionResult> CreateAsync()
{
ViewBag.MeetingCentersList = await _meetingCenterReposiotry.GetItemsAsync();
return View();
}
Create View:
#model Employee
#using (Html.BeginForm("Create", "Home", FormMethod.Post, new { #class = "form-horizontal" }))
{
...
<div class="form-group">
#Html.LabelFor(MeetingCenter => Model.MeetingCenter, new { #class = "control-label" })
#Html.DropDownListFor(MeetingCenter => Model.MeetingCenter, new SelectList(ViewBag.MeetingCentersList, "MeetingCenterId", "Name"), new { #class = "form-control" })
</div>
...
}
Piece of Employee Model
public class Employee
{
...
[JsonProperty(PropertyName = "id")]
public string EmployeeId { get; set; }
[JsonProperty(PropertyName = "meetingCenter")]
public MeetingCenter MeetingCenter { get; set; }
...
}
Piece of MeetingCenter Model
public class MeetingCenter
{
...
[JsonProperty(PropertyName = "id")]
public string MeetingCenterId { get; set; }
...
}
With your current code, the DropDownListFor helper will render a SELECT element with options, which has the MeetingCenterId as the value attribute and the Name as the Text. The SELECT element's name attribute value will be MeetingCenter. So when the form is submitted the form data will look like this
MeetingCenter: 2
Assuming user selected the option with value "2".
But the MeetingCenter property of your view model(Employee) is not a numeric type, it is a complex type(MeetingCenter). So model binder cannot map this value to MeetingCenter property of your view model.
You can render the SELECT element with the name MeetingCenter.MeetingCenterId and then model binder will be able to map the posted form data as the input element name matches with the naming-structure of your view model.
So you should render something like this inside your form.
<select name="MeetingCenter.MeetingCenterId">
</select>
You can generate the above markup by using the SELECT tag helper and specifying MeetingCenter.MeetingCenterId as the asp-for property.
<select asp-for="MeetingCenter.MeetingCenterId"
asp-items="#(new SelectList(ViewBag.MeetingCentersList,
"MeetingCenterId", "Title"))">
</select>
Now when the form is submitted, it will populate the MeetingCenter property of your view model and it's MeetingCenterId property.
If you want the full MeetingCenter property to be populated (properties other than MeetingCenterId, get the full object by querying the data provided by _meetingCenterReposiotry.GetItemsAsync() using the MeetingCenterId available to you in the HttpPost action. Something like this
var id = viewModel.MeetingCenter.MeetingCenterId;
var items = await _meetingCenterReposiotry.GetItemsAsync();
var item = items.FirstOrDefault(a=>a.MeetingCenterId==id);
// User item now
// May be do somethig like : viewModel.MeetingCenter = item;
I also suggest you to use the correct types. If MeetingCenterId is numeric value, use int as type instead of string

MVC5 Html.DropDownListFor Does not select correct value

I'm doing something horribly simple and it isn't working.
#Html.DropDownListFor(m => m.ContentDefinitionID, Model.ContentBoxesSelectList, new {#class = "form-control"})
The ContentDefinitionID is a UInt64 (although I've tried an int)
I use this same select list for 4 different controls on the page.
If my Model.ContentDefinition is set to 4, (which would be test4 in the drop down) then it SHOULD pull that selected value from the Model, NOT from the selectList right? Someone else said that it ignores the value on the SelectList when you use the m=>m.X syntax - which makes sense.
But it always selects the first one. I tried adding another parameter for what it should select, but it wants the text, not the value and I hate to have to lookup the text. (And I'm not sure this will post back correctly if I do that anyway)
I'm about to go create some JQuery code to set the default values for everything -but that is crazy. This is the obvious behavior of the DropDownListFor() method, kind of a 'duh' thing - why doesn't it work? Do I need to create a custom SelectList for every control that exists on the page?
--- Update, the model etc:
class PageModel
{
Int64 ContentDefinitionID {get;set;}
SelectList ContentBoxesSelectList {get;set;}
}
Controller init for model:
model.ContentDefinitionID = 4; // In real situation, this is an
array of N model.ContentBoxesSelectList = new SelectList( from
ContentDefinitionDocument doc in allContentBoxes
where doc.Size == size
select new {Name = doc.Name, id = doc.DefinitionID}, "Id", "Name");
Rendered Output (from it as an array):
selected value should be: 1
<select class="form-control" data-val="true" data-val-number="The field ContentDefinitionID must be a number." data-val-required="The ContentDefinitionID field is required." id="ContentBoxes_0__ContentDefinitionID" name="ContentBoxes[0].ContentDefinitionID" style="width:25%;float:left"><option value="1">Test1</option>
<option value="2">Test 2</option>
<option value="4">Test 4</option>
<option value="0">Test 0</option>
</select>
And None of them are selected.
From the html your generating (name="ContentBoxes[0].ContentDefinitionID"), you are using this in a for loop. Unfortunately DropDownListFor() does not work as expected in a for loop and you need to use a custom EditorTemplate for your model (its been reported as a bug but not yet fixed).
You have not posted your models, but assuming
public class ContentBox
{
public int ContentDefinitionID { get; set; }
....
}
public class ContentBoxesVM
{
public IEnumerable<ContentBox> ContentBoxes { get; set; }
public SelectList ContentBoxesSelectList { get; set; }
}
Side note: Its only necessary to generate one SelectList (rather that one for each object in the collection)
Controller
public ActionResult Edit()
{
ContentBoxesVM model = new ContentBoxesVM();
// populate model, for example
model.ContentBoxes = new List<ContentBox>()
{
new ContentBox { ContentDefinitionID = 4 }
};
model.ContentBoxesSelectList = new SelectList(...);
return View(model);
}
Editor Template (/Views/Shared/EditorTemplates/ContentBox.cshtml)
#model ContentBox
....
#Html.DropDownListFor(m => m.ContentDefinitionID, (SelectList)ViewData["contentBoxesList"])
...
Main view
#model ContentBoxesVM
#using(Html.BeginForm())
{
....
// pass the select list as additional view data to the editor template
#Html.EditorFor(m => m.ContentBoxes, new { contentBoxesList = Model.ContentBoxesSelectList })
....
<input type="submit" />
}
You can achieve what you want by changing your model:
class PageModel
{
Int64 ContentDefinitionID {get;set;}
List<ContentDefinitionDocument> ContentBoxesList {get;set;}
}
and in controller:
array of N model.ContentBoxesList = allContentBoxes.Where(doc => doc.Size == size).ToList();
and in View create SelectList this way:
#Html.DropDownListFor(m => m.ContentDefinitionID,
new SelectList(Model.ContentBoxesSelectList,
"DefinitionID",
"Name",
Model.ContentDefintionID),
new {#class = "form-control"})

Data binding of ViewModel to Razor View then get POST ViewModel to Controller

UPDATED POST
I am currently developing a Survey application that uses MVC4, Razor, EF5. There will be various surveys an will be utilizing one view. The Models for the application comes from an existing database. Apart from that, I created a separate Entities for the following:
SurveyDisplay - Model for elements that will appear on the survey page because it consists of different languages.
SurveyInfo - Model for information where data comes from a web service.
SurveyQuestion - Model for the questionnaires of the survey.
SurveyChoices - Model for the choices of each question in the survey.
SurveyAnswers - Model to retrieve choices of answers
UPDATE IMAGE(Added SurveyAnswers)
Kindly refer to the image below for the following fields:
I am able to get the particular values to display in my page which is a Razor, but upon POST. I am getting ModelState.IsValid == false. All models are null except for SurveyAnswers.
Here's how my code goes so far:
SurveyRepository:
For this part, I imported stored procedures to get data from the database.
public List<SurveyQuestion> GetQuestions(int surveyid)
{
using (var ctx = new ICSDBContext())
{
return ctx.GetSurveyQuestions(surveyid).ToList<SurveyQuestion>();
}
}
public List<SurveyChoice> GetChoices(int surveyid)
{
using (var ctx = new ICSDBContext())
{
return ctx.GetSurveyChoices(surveyid).ToList<SurveyChoice>();
}
}
public List<SurveyDisplay> GetSurveyDisplay(int surveyid)
{
using (var ctx = new ICSDBContext())
{
return ctx.GetSurveyDisplay(surveyid).ToList<SurveyDisplay>();
}
}
SurveyController:
using ICS.Repositories;
using ICS.ViewModels;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
public class SurveyController : Controller
{
SurveyRepository surveyRepository = new SurveyRepository();
SurveyViewModel surveyViewModel = new SurveyViewModel();
[HttpGet]
public ActionResult Index(int surveyid, string rowid)
{
var surveyDisplay = surveyRepository.GetSurveyDisplay(surveyid);
var surveyQuestions = surveyRepository.GetQuestions(surveyid);
var surveyChoices = surveyRepository.GetChoices(surveyid);
string headerText = "";
string messageBody = "";
string buttonText = "";
foreach (var item in surveyDisplay)
{
headerText = item.HeaderText;
messageBody = item.MessageBody;
buttonText = item.ButtonText;
}
surveyViewModel.HeaderText = Server.HtmlEncode(headerText);
surveyViewModel.MessageBody = Server.HtmlEncode(messageBody);
surveyViewModel.Buttontext = Server.HtmlEncode(buttonText);
surveyViewModel.SurveyQuestions = surveyQuestions;
surveyViewModel.SurveyChoices = surveyChoices;
return View("Survey", surveyViewModel);
}
//Not the actual code yet. I'm trying to check in this action whether there is POSTed data or none.
[HttpPost]
}
SurveyViewModel
public class SurveyViewModel
{
public List<SurveyInfo> SurveyInfo { get; set; }
public string HeaderText { get; set; }
public string MessageBody { get; set; }
public string Buttontext { get; set; }
public List<SurveyQuestion> SurveyQuestions { get; set; }
public List<SurveyChoice> SurveyChoices { get; set; }
public List<SurveyAnser> SurveyAnsers { get; set; }
}
Razor View
<h2>#Html.DisplayFor(model => model.HeaderText)</h2>
<div id="info">
<p>#Html.DisplayFor(model => model.MessageBody)</p>
<div class="margTop20">
<ul>
#for (var info = 0; info < Model.SurveyInfo.Count(); info++)
{
<li>
<span>Case ID: </span>
<b>#Html.DisplayFor(model => model.SurveyInfo[info].SRNumber)</b>
</li>
<li>
<span>Description: </span>
<b>Others</b>
</li>
<li>
<span>Problem Category: </span>
<b>#Html.DisplayFor(model => model.SurveyInfo[info].ProblemSubCategory)</b>
</li>
<li>
<span>Product: </span>
<b>#Html.DisplayFor(model => model.SurveyInfo[info].Product)</b>
</li>
<li>
<span>Method of Service: </span>
<b>#Html.DisplayFor(model => model.SurveyInfo[info].SupportType)</b>
</li>
}
</ul>
</div>
</div>
#for (var question = 0; question < Model.SurveyQuestions.Count(); question++)
{
<div id="#("question" + ConvertNumberToWords.Translate(question))" class="#(Convert.ToBoolean(Model.SurveyQuestions[question].Subquestion) == true ? "subquestion" : "questions")">
<p>
<b>
#Html.DisplayFor(model => model.SurveyQuestions[question].TheQuestion)
</b>
</p>
#Html.HiddenFor(model => model.SurveyAnswers[question].QuestionID)
#if (Convert.ToBoolean(Model.SurveyQuestions[question].Mandatory) == true)
{
<p><span id="#("errorQuestion" + ConvertNumberToWords.Translate(question))" class="errorMsg">*Please choose your answer</span></p>
}
#for (var choice = 0; choice < Model.SurveyChoices.Count(); choice++)
{
if (Model.SurveyQuestions[question].QuestionID == Model.SurveyChoices[choice].QuestionID)
{
if (Model.SurveyChoices[choice].isStyleOptBox)
{
var choicesGroup = (from c in Model.SurveyChoices where c.QuestionID == Model.SurveyQuestions[question].QuestionID select new { c.ChoicesID, c.ChoicesName });
#Html.Raw("<ul>")
#Html.Raw("<li>")
#Html.RadioButtonForSelectList(model => model.SurveyAnswers[question].ChoiceID, new SelectList(choicesGroup, "ChoicesID", "ChoicesName"))
#Html.Raw("</li>")
#Html.Raw("</ul>")
break;
}
else if (Model.SurveyChoices[choice].isStyleChkBox)
{
var choicesGroup = (from c in Model.SurveyChoices where c.QuestionID == Model.SurveyQuestions[question].QuestionID select new { c.ChoicesID, c.ChoicesName });
#Html.Raw("<ul>")
#Html.Raw("<li>")
#Html.CheckBoxListFor(model => model.SurveyAnswers[question].ChoiceID, model => choicesGroup, model => model.ChoicesID, model => model.ChoicesName, model => false, Position.Vertical)
#Html.Raw("</li>")
#Html.Raw("</ul>")
break;
}
else if (Model.SurveyChoices[choice].isStyleCboBox)
{
}
else
{
<div class="margTop20">
<p>
#*<textarea cols="" rows="5" class="form-control"></textarea>*#
#Html.TextAreaFor(model => model.SurveyAnswers[question].Comment, new { rows = "5", cols = "0", #class = "form-control" })
</p>
</div>
}
}
}
</div>
}
</div>
<input id="hidQuestionCount" type="hidden" value="#Model.SurveyQuestions.Count()" />
<div>
#*<a class="btn btn-primary" href="#myModal" id="btnSubmit">#Model.Buttontext</a> #Url.Action("Submit", "SaveSurvey", Model)*#
<input id="btnSubmit" class="btn btn-primary" type="submit" value="#Model.Buttontext" />
</div>
If you will notice, I am using a custom Html Helper to render radio button groups which is RadioButtonSelectListFor which is very handy in this scenario. I am able to bind and get the value of the selected control as you can see in the image below:
Secondly, I am also using the Html Helper package Hmtl Helper CheckBoxListFor to display group of checkboxes to make multiple selections and submit it. But the problem is, I am getting 1 value among all checkboxes which causes me real pain and headache. When there is 2 or more checkboxes checked, only the first item is being returned, you may refer to the image below:
For the comments value, I have no problem with it as I can get the values. As seen on the image:
I also have one problem, I need to bind QuestionID in SurveyAnswers because it is used for reporting purpose.
To sum up, below are my things to achieve:
OBJECTIVES:
Bind QuestionID to model SurveyAnswers
Return all checkboxes value and add it to list SurveyAnswers
If possible, make ModelState.IsValid to be true. If not, I won't be validating model to get the list of SurveyAnwsers
I really want to get this thing to work. I have been doing a lot of research just to get it going, but no progress yet. Kindly help me guys! Any inputs/ suggestions will be highly appreciated.
Thank you very much!
The issue is with these lines in your view model:
public List<SurveyInfo> SurveyInfo { get; set; }
...
public List<SurveyQuestion> SurveyQuestions { get; set; }
public List<SurveyChoice> SurveyChoices { get; set; }
Because you're referencing full entities, presumably each with their own required properties, you need to ensure that each of those required properties is posted back with some value or it invalidates your entire model, because that individual entity is invalid. However, you're not actually creating any of these entities, but rather just displaying existing instances. As a result, you should be using a view model for each of these as well. The only thing that should be required at all on your view model or anything referenced by your view model is the actual data you want to collect from the user.
UPDATE
This is all about how the model binder works and how it determines if your model is valid or not. It looks at it as if you would want to save this entire thing to a database, as if you had a table that matched up with your view model (even though you don't actually). So, in order for the view model to be "valid", it would have to be able to save everything else on the view model as well (your SurveyInfo, SurveyQuestions and SurveyChoices properties. If there's any required properties on any of those classes, that isn't posted back (which there of course are), then they are invalid, and your view model is invalid as a result. Now, that said, there's conditions. Simply because you attached a list of SurveyQuestions doesn't mean that you have to have a list of valid SurveyQuestion instances in your POST body to allow the view model to validate. If a list or relationship is null, then it is not validated (assuming that the list or instance, itself, is not required). So, really where you're going wrong here is in posting back partial instances of these things. Because it's ending up with a list of incomplete SurveyQuestion instance, it's invalid, whereas if you simply posted nothing back at all, it would be valid.
In other words, you can keep the lists as they are, to pull information from as you need it in your view, but, you need to attach POST data somewhere else. Create another list property like:
public List<SurveyAnswerViewModel> SurveyAnswers { get; set; }
And that would be the only thing that you posted to. Then, as long the user fills out the survey correctly as defined in SurveyAnswerViewModel, you'd have a valid view model. Just be aware that since you're not posting the other lists back, you have to repopulated them should the view need to be returned back to correct an error.

ASP MVC - Get data from partial view on Create

Im using ASP.Net MVC 5.
I have two simple classes; Student and Course, like this;
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Course
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
I want to create a new Course with optional many Students.
The student(s) form/view will be rendered as a partail view (insde the Course-view).
Right now I have a Create-View that is strongly type to Course.
This view only have 1 textbox - name of the Course.
I render the partial view that is strongly typed to Student.
To simplify i just want to add 1 student to the List.
I would like pass the student data to the course object and then "move on" to the controller.
Can anyone help me with this approach of passing data from a partitial view, or give me a hint of how its done in MVC? ;)
Ok found out what I was doing wrong. First off I donwloaded the Html helper, BeginCollectionItem. Real nice if you want to dynamically add and remove fields/textboxes that will be added to your model.
First off send an empty object to to your view to work with (from your controller).
I just passed in a new Course object. Ctor of Course creates a new List with 1 student object.
Then i used RenderPartial to display a partailview + the student item.
#foreach(var student in Model.Students)
{
RenderPartial("_Student", student);
}
This view looks like this:
#model Project.Data.Entities.Student
<div class="AddStudent form-group">
#using (Html.BeginCollectionItem("students"))
{
#Html.Label("Name:", new { #class = "control-label col-md-2" })
<div class="col-md-8">
#Html.TextBoxFor(x => x.Name)
<button type="button" class="deletButton btn btn-default">Remove</button>
#Html.ValidationMessageFor(model => model.Name)
</div>
}
</div>
I render a button that is hooked up to delete (jquery) the student field.
When i want to add more students to my Course i just use an ajax call to add more partial "_Student" views.
<div>
#Ajax.ActionLink("Add more...", "NewStudentRow", "Course", new AjaxOptions()
{
InsertionMode = InsertionMode.InsertAfter,
UpdateTargetId = "students"
}, new { #class = "btn btn-default" })
</div>
The NewStudentRow method in my controller looks like this:
public PartialViewResult NewStudentRow ()
{
return PartialView("_Student", new Student());
}
Pretty simple if you just use the http://www.nuget.org/packages/BeginCollectionItem/
You can solve this by having more than one partial view..
Pseudo-code:
CourseView:
<TextBox>#Model.Name</TextBox>
#foreach(var student in Model.Students)
{
RenderPartial("ShowStudent");
}
RenderPartial("AddStudent");
The AddStudentView conains all fields you need to provide to save a student to database. In the action you take the input parameters, save the new student and redirect ( something like return RedirectToAction("Course", new { id = student.CourseId }) ) to the Course view. The course view will then be loaded including the new student.
You could also do all of this with ajax to prevent postback, but as you haven't specified any desire to prevent postback I think this would be a good solution.

Handling MVC form submission to database

So I'm loosely following the Music Store tutorial. I created the StoreManagerController on pg. 54ish. And it created a view with the Create, Deleted, Edit, etc. It's saving some stuff to the database, namely my EditFor controls, but nothing else.
I have multiple DropDownListFor controls, populated by both tables in the database and also Active Directory user data. I'm not sure how to get these to save. Here is my abridged code. Thanks for the help.
View:
<div class="createTopInner">
<div class="editor-label">
#Html.LabelFor(model => model.test.Category)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.CategoryId, Model.CategoryItems, "")
#Html.ValidationMessageFor(model => model.test.Category)
</div>
</div>
Controller:
public ActionResult Create()
{
// These four lines get active directory users
ActiveDirectoryModel ads = new ActiveDirectoryModel();
ViewBag.assignedto = ads.FetchContacts();
ViewBag.coassignedto = ads.FetchContacts();
ViewBag.notifyto = ads.FetchContacts();
var model = Populate();
return View(model);
}
[HttpPost]
public ActionResult Create(CreateViewModel model)
{
if (ModelState.IsValid)
{
db.TestItems.AddObject(model.test);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}
public CreateViewModel Populate()
{
var model = new CreateViewModel
{
CategoryItems =
from c in new IntraEntities().CategoryItems.ToList()
select new SelectListItem
{
Text = c.Name,
Value = c.ID.ToString()
}
};
return model;
}
Model:
public class CreateViewModel
{
public Intra.Models.TestItem test{ get; set; }
public int CategoryId { get; set; }
public IEnumerable<SelectListItem> CategoryItems { get; set; }
}
The problem seems to be that, while most of your inputs map to properties on test, the CategoryId doesn't. Not knowing anything about your entity models, it's difficult to say, but I'd hazard a guess that you need to retrieve the corresponding Category from the database and add that to your TestItem instance before you persist it. If you do have a CategoryId property on your TestItem instance, you could just set it, but I'm guessing that you don't because otherwise you would have used it directly (as you do for the Category label) instead of adding a property to the view model.
If you have access to it / know much about stored procedures, it is much better to use Store procedures inside the database and then call them within Entity. It's much more loosely coupled and easier to make changes to without recompiling code.

Categories