Getting a list of radio button values in ASP MVC 3 - asp.net-mvc-3

I am developing a page for rating questions.
In the view, I have a list of questions and 5 radio buttons in front of each one of them.
<input name="evalId" type="hidden" value="#Model.Evaluation.EvalId" />
foreach (var question in questionList)
{
<input name="questionId" type="hidden" value="#question.QuestionId" />
<div class="row_star" style="border-bottom : 0 none; background: none;">
#if (!String.IsNullOrEmpty(question.QuestionTitre))
{
<p>#question.QuestionTitre.TrimEnd()</p>
}
#* here goes the code for 5 radio buttons*#
}
Now, in my controller I want to be able to know which radio button was checked for each question.
How can I do that ?
Here is my ViewModel
public class EvaluationViewModel
{
/// <summary>
///
/// </summary>
public EvalEvaluation Evaluation
{
get;
set;
}
/// <summary>
///
/// </summary>
public Dictionary<EvalQuizz, List<EvalQuestion>> EvalQuizzQuestionList
{
get;
set;
}
}

Assuming your ViewModel is like this
public class Question
{
public int ID { set; get; }
public string QuestionText { set; get; }
public List<Answer> Answers { set; get; }
public int SelectedAnswer { set; get; }
public Question()
{
Answers = new List<Answer>();
}
}
public class Answer
{
public int ID { set; get; }
public string AnswerText { set; get; }
}
public class Evaluation
{
public List<Question> Questions { set; get; }
public Evaluation()
{
Questions = new List<Question>();
}
}
And in your GET action method, you will return the viewmodel back to the view with some questions and answers filled in it. In the code below I've hardcoded the questions and answers. You may get it from your Repositary/Service layer.
public ActionResult Index()
{
var evalVM = new Evaluation();
//the below is hardcoded for DEMO. you may get the data from some
//other place and set the questions and answers
var q1=new Question { ID=1, QuestionText="What is your favourite language"};
q1.Answers.Add(new Answer{ ID=12, AnswerText="PHP"});
q1.Answers.Add(new Answer{ ID=13, AnswerText="ASP.NET"});
q1.Answers.Add(new Answer { ID = 14, AnswerText = "Java" });
evalVM.Questions.Add(q1);
var q2=new Question { ID=2, QuestionText="What is your favourite DB"};
q2.Answers.Add(new Answer{ ID=16, AnswerText="SQL Server"});
q2.Answers.Add(new Answer{ ID=17, AnswerText="MySQL"});
q2.Answers.Add(new Answer { ID=18, AnswerText = "Oracle" });
evalVM.Questions.Add(q2);
return View(evalVM);
}
Now we will create an Editor Template to render our Question. so go to your View Folder and create a folder called EditorTemplates under the folder with your current controller name.
Add a view to the EditorTemplates folder and give the same name as the class name we want to represent. ie : Question.cshtml
Now put this code in the editor tempalte
#model YourNameSpace.Question
<div>
#Html.HiddenFor(x=>x.ID)
#Model.QuestionText
#foreach (var a in Model.Answers)
{
<p>
#Html.RadioButtonFor(b=>b.SelectedAnswer,a.ID) #a.AnswerText
</p>
}
</div>
Now go to our main view and use EditorTemplate html helper method to bring the EditorTemplate we created to the main view.
#model YourNameSpace.Evaluation
<h2>Index</h2>
#using (Html.BeginForm())
{
#Html.EditorFor(x=>x.Questions)
<input type="submit" />
}
Now in your HttpPost you can check the posted model and get the selected radio button (SelectedAnswer) value there
[HttpPost]
public ActionResult Index(Evaluation model)
{
if (ModelState.IsValid)
{
foreach (var q in model.Questions)
{
var qId = q.ID;
var selectedAnswer = q.SelectedAnswer;
//Save
}
return RedirectToAction("ThankYou"); //PRG Pattern
}
//reload questions
return View(model);
}
If you use visual studio breakpoints, you can see the values posted. Thanks to MVC Model binding :)
You can read about it and download a working sample here.

Related

How Can I Get the Controller to Register the Value of Votes to the Database?

I'm desperately trying to find out a solution towards solving this feature, but I haven't really got any useful help anywhere so far.
I'm working on a ASP.NET Framework MVC project, where I try to implement a simple rating feature for a Quiz class. But I'm kind of lost when it comes to the AJAX part and the Controller.
I can't figure it out how to implement this so every votes related to a specific Quiz gets registered in the Notification table.
I created a Notification class inside Model to store the results of Votes:
public class Notification
{
public int Id { get; set; }
[Required]
public Quiz Quiz { get; set; }
public int UpVoteCount { get; private set; }
public int DownVoteCount { get; private set; }
public int Score => UpVoteCount - DownVoteCount;
public void UpVote()
{
UpVoteCount++;
}
public void DownVote()
{
DownVoteCount++;
}
}
Then in the folder under Controller/API/QuizsController.cs I have implemented this action method, which I'm unsure of it's implementation. I'm kind of lost in this part! :
[HttpPost]
public IHttpActionResult Vote(int id)
{
var userId = User.Identity.GetUserId();
var quiz = _context.Guizzes.Single(m => m.Id == id && m.AuthorId == userId);
var notification = new Notification
{
Quiz = quiz, UpVote, DownVote, Score
};
_context.SaveChanges(); // save to the database
return Ok();
}
Then in the View I created MyQuiz.cshtml file and in there I have implemented the below html and AJAX code, but again this part certainly lacks some basic constructions to connect the controller to register the votes in the database.:
#model IEnumerable<VikingNotes.Models.Quiz>
#{
ViewBag.Title = "Home Page";
}
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.4.2/css/all.css" integrity="sha384-/rXc/GQVaYpyDdyxK+ecHPVYJSN9bmVFBvjA/9eOB+pb3F2w2N6fc5qB9Ew5yIns" crossorigin="anonymous">
<ul class="quizs verOffset7">
#foreach (var quiz in Model)
{
#*<li> #quiz.Creation - #quiz.Author.UserName </li>*#
<li>
<!-- Star Rating with font awesome-->
<div class="rating-star ">
#for (int i = 0; i < 5; i++)
{
<i class="fas fa-star"></i>
}
</div>
<!-- up-down arrow with font awesome-->
<div class="article">
<a href="voteUp" i class='fas fa-caret-up' style='font-size:40px;color:darkgrey'></a> <br>
<a href="votedown" class='fas fa-caret-down' style='font-size:40px;color:darkgrey'></a>
</div>
</li>
}
</ul>
#section scripts
{
<script>
jQuery(function ($) {
// Hook up our vote handlers
$("a.vote").live('click', voteClick);
function voteClick(event) {
var voteLink, voteType, item, itemId;
// Regardless of the below, we handle the event, so "consume" it
event.stopPropagation();
event.preventDefault();
// Get the anchor element, wrapped in a jQuery instance
voteLink = $(this);
// See if the vote has already been done or is in progress
if (voteLink.hasClass('done') || voteLink.hasClass('inprogress')) {
// Ignore the click, possibly tell the user why
return;
}
// Get the vote type
voteType = voteLink.hasClass('up') ? 'up' : 'down';
// Get the item we're voting on
item = voteLink.closest('.article');
// Get its ID
itemId = item.attr('data-itemid');
// If we didn't get an ID...
if (!itemId) {
// ...report error
return;
}
// Mark "in progress" and initiate the vote; action continues
// in our callbacks below
voteLink.addClass('inprogress');
$.ajax({
url: 'savevote',
data: { itemId: itemId, voteType: voteType },
type: 'POST',
success: votePostSuccess,
error: votePostError
});
// Called when the POST is successful
function votePostSuccess(response) {
// The POST worked
voteLink.removeClass('inprogress');
// Did things work on the server?
if (response === "ok") { // Or whatever
// Yes, the vote was successfully recorded
voteLink.addClass('done');
}
else {
// Report an error to the user, the server couldn't record the vote
}
}
// Called when the POST fails for some reason (HTTP errors)
function votePostError(xhr, statusText, err) {
// Not in progress anymore
voteLink.removeClass('inprogress');
// Report error to user
}
}
});
</script>
}
The Quiz Model looks like this:
public class Quiz
{
public int Id { get; set; }
public ApplicationUser User { get; set; }
[Required]
public string UserId{ get; set; }
[Required, MaxLength(200)]
public string Title { get; set; }
[Required]
[StringLength(Int32.MaxValue)]
public string Description { get; set; }
public DateTime Creation { get; set; }
public Genre Genre { get; set; }
public IEnumerable Genres { get; set; }
[Required]
public int GenreId { get; set; }
}
I also tried another approach:
I tried to adopt this tutorial [https://www.jqueryscript.net/other/Awesome-Rating-System.html][1]
with my project, but I couldn't get any results. I applied the instructions on the link and I downloaded the libraries and added to the project.
Please, some helpful souls!
(I've to add that it's my first project with mvc, razor and AJAX)
If what you want to do is simply send the star rating to the controller. You can do the following.
#for (int i = 1; i <= 5; i++)
{
<a onclick"javascript: voteClick(#i, //ID of the quiz)">
<i class="fas fa-star"></i>
</a>
}
That should send the vote rating and the id of the quiz to the function. From there you can simply use an ajax post to post it to the controller method.
EDIT: Note that you will need to change the parameters of your javascript function to:
function voteClick(vote, id)
{
//Function stuff
}

How to update hierarchical ViewModel?

I am stuck with this problem.
I have a model AssessmentModel defined like this:
public class AssessmentModel
{
public Respondent Respondent { get; set; }
public List<CompetencyModel> Competencies { get; set; }
}
public class CompetencyModel
{
public int Id { get; set; }
public string Name { get; set; }
public List<ResultModel> Results { get; set; }
}
public class ResultModel
{
public int Id { get; set; }
public int Score { get; set; }
}
All I need is to set value to the Score property of ResultModel.
Score is the only editable property here.
And I have just 1 View only, this view has a #model List, it displays a list of CompetencyModel items with Edit button for each one.
When I click the Edit button, the Id of CompetencyModel is passed to the same View, and the View draws an Edit form for ResultModel items that belong to the selected CompetencyModel.
However the form for ResultModel items exists on the same View, and the model of the View is still #model List.
How can I get to the Score property by using bindable Html.EditorFor(m=>m.Score) helper for each ResultModel item?
The View is defined like this:
#model List<CompetencyModel>
#foreach(var comp in Model)
{
<p>#comp.Name</p>
Edit
}
In the controller I set ViewBag.CurrentId = comp.Id, and at the bottom of the View:
if(ViewBag.CurrentId != null) //draw a form for ResultModel items
{
// What should I do now?
// how cant I use Html.EditorFor(m=>...) if the Model is still List<CompetencyModel>
}
I need to get to a single ResultModel entity to set a value to a Score property.
Thank you.
You should be able to get this done using Linq. Consider having the following code segment in the your last if statement
var result = Model.Results.FirstOrDefault(r => r.Id == ViewBag.CurrentId);
I dont have a IDE with me, so watchout for syntext errors

Why is {System.Web.Mvc.SelectList} DropDownList SelectedValue

I have created a DropDownList, as described in my last question here
I have been trying to figure out how to get the selected value of the list. I used the answer that was provided but the only thing it returned was {System.Web.Mvc.SelectList}
I debugged it and sure enough the string that was in the "Value" column was {System.Web.Mvc.SelectList}
What am I doing wrong here? I have been miserably failing at MVC and am new at it.
Thank you for the help
Your Action in your controller should look like this:
[HttpPost]
public ActionResult Index(int DropOrgId)
{
System.Diagnostics.Debugger.Break();
return null;
}
The important thing to note is that "DropOrgId" is the same as the string name you passed into #Html.DropDownList("DropOrgID") in your view. This name will store the value of the input from the HTML input control, in this case the
The source will be:
<select id="DropOrgID" name="DropOrgID">...</select>
The id of the input control is how the MVC framework will match up the value of that control to the parameter of the action you are looking for.
Here is a sample app that shows it:
Class
public class Organization
{
public int OrganizationID { get; set; }
public string Name { get; set; }
}
Controller
public class HomeController : Controller
{
public ActionResult Index()
{
var orgs = new List<Organization>();
foreach (var count in Enumerable.Range(1, 10))
{
var newOrg = new Organization();
newOrg.OrganizationID = count;
newOrg.Name = "Organization " + count.ToString();
orgs.Add(newOrg);
}
ViewBag.DropOrgID = new SelectList(orgs, "OrganizationID", "Name", 3);
return View();
}
[HttpPost]
public ActionResult Index(int DropOrgID)
{
//You can check that this DropOrgID contains the newly selected value.
System.Diagnostics.Debugger.Break();
return null;
}
}
Index View
<h2>Index</h2>
#using (Html.BeginForm())
{
#Html.DropDownList("DropOrgID")
<br />
<br />
<input type="submit" value="Save" />
}

How does one populate a displayed form using data from an associated database entry that is selected via a drop-down

I have the following code that allows a teacher to see a drop-down list of available courses to teach, listed by name. When a teacher selects a dropdown option I would like a form on the view to auto-populate with default values representing the selected course. What is the most efficient way to populate the fields?
note: When "Custom" is selected in the drop-down, I want the form that is displayed below the dropdown to have nothing but blank spaces.
CourseController
// GET: /Course/ApplyToTeach
public ActionResult ApplyToTeach()
{
var course = db.CourseTemplates;
var model = new ApplyCourseViewModel
{
Courses = course.Select(x => new SelectListItem
{
Value = x.Title,
Text = x.Title,
})
};
return View(model);
}
ApplyToTeachViewModel
public class ApplyToTeachViewModel
{
[Display(Name = "selected course")]
public string SelectedCourse { get; set; }
public IEnumerable<SelectListItem> Courses { get; set; }
}
ApplyToTeach (view) - note that all I have here currently is the drop-down menu, I am looking for the most efficient way to add the auto-populating form below this drop-down.
<h2>ApplyToTeach</h2>
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Apply To Teach</legend>
<div class="editor-label">
Which class would you like to teach? (select "Custom" if you would like to submit a customized class)
</div>
<div class="editor-field">
#Html.DropDownListFor(x => x.SelectedCourse, Model.Courses, "Custom")
#Html.ValidationMessageFor(model => model.Courses)
</div>
<p>
<input type="submit" value="Submit" />
</p>
</fieldset>
}
The data for the drop-down fields comes from the following model -
CourseTemplates
public class CourseTemplates
{
public int CourseTemplatesID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public int AttendingDays { get; set; } // 10, 8, 3, or custom
public int AttendanceCap { get; set; } // default of 30
public string Location { get; set; }
public string Description { get; set; }
}
The form is actually going to be submitted as a "Course" model, not the "CourseTemplates" model, the "Course" model has more data fields than the "CourseTemplates" model - such as the following:
public DateTime StartDate { get; set; }
public bool Approved { get; set; }
etc. . .
What I have in mind as far as user-experience is that an administrator will go through beforehand and add in a number of possible course options, simply to ease the application process for most teachers (so they don't have to type every detail for every class they apply to teach), but I want the teacher to be able to edit any information before submitting the course for review by an administrator.
Any tips?

MVC3 EditorFor dynamic property (or workaround required)

I am building a system which asks questions and receives answers to them. Each question can have an aswer of its own type. Let's limit it to String and DateTime for now. In Domain, question is represented the following way:
public class Question
{
public int Id
{
get;
set;
}
public string Caption
{
get;
set;
}
public AnswerType
{
get;
set;
}
}
, where AnswerType is
enum AnswerType
{
String,
DateTime
}
Please note that actually I have much more answer types.
I came up with an idea of creating a MVC model, deriving from Question and adding Answer property to it. So it has to be something like this:
public class QuestionWithAnswer<TAnswer> : Question
{
public TAnswer Answer
{
get;
set;
}
}
And here start the problems. I want to have a generic view to draw any question, so it needs to be something like that:
#model QuestionWithAnswer<dynamic>
<span>#Model.Caption</span>
#Html.EditorFor(m => m.Answer)
For String I want to have simple input here, for DateTime I am going to define my own view. I can pass the concrete model from the controller. But the problem is that on the rendering stage, naturally, it cannot determine the type of Answer, especially if it is initially null (default for String), so EditorFor draws nothing for String and inputs for all properties in DateTime.
I do understand the nature of the problem, but is there any elegant workaround? Or I have to implement my own logic for selecting editor view name basing on control type (big ugly switch)?
Personally I don't like this:
enum AnswerType
{
String,
DateTime
}
I prefer using .NET type system. Let me suggest you an alternative design. As always we start by defining out view models:
public abstract class AnswerViewModel
{
public string Type
{
get { return GetType().FullName; }
}
}
public class StringAnswer : AnswerViewModel
{
[Required]
public string Value { get; set; }
}
public class DateAnswer : AnswerViewModel
{
[Required]
public DateTime? Value { get; set; }
}
public class QuestionViewModel
{
public int Id { get; set; }
public string Caption { get; set; }
public AnswerViewModel Answer { get; set; }
}
then a controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new[]
{
new QuestionViewModel
{
Id = 1,
Caption = "What is your favorite color?",
Answer = new StringAnswer()
},
new QuestionViewModel
{
Id = 1,
Caption = "What is your birth date?",
Answer = new DateAnswer()
},
};
return View(model);
}
[HttpPost]
public ActionResult Index(IEnumerable<QuestionViewModel> questions)
{
// process the answers. Thanks to our custom model binder
// (see below) here you will get the model properly populated
...
}
}
then the main Index.cshtml view:
#model QuestionViewModel[]
#using (Html.BeginForm())
{
<ul>
#for (int i = 0; i < Model.Length; i++)
{
#Html.HiddenFor(x => x[i].Answer.Type)
#Html.HiddenFor(x => x[i].Id)
<li>
#Html.DisplayFor(x => x[i].Caption)
#Html.EditorFor(x => x[i].Answer)
</li>
}
</ul>
<input type="submit" value="OK" />
}
and now we can have editor templates for our answers:
~/Views/Home/EditorTemplates/StringAnswer.cshtml:
#model StringAnswer
<div>It's a string answer</div>
#Html.EditorFor(x => x.Value)
#Html.ValidationMessageFor(x => x.Value)
~/Views/Home/EditorTemplates/DateAnswer.cshtml:
#model DateAnswer
<div>It's a date answer</div>
#Html.EditorFor(x => x.Value)
#Html.ValidationMessageFor(x => x.Value)
and the last piece is a custom model binder for our answers:
public class AnswerModelBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
var typeValue = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + ".Type");
var type = Type.GetType(typeValue.AttemptedValue, true);
var model = Activator.CreateInstance(type);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, type);
return model;
}
}
which will be registered in Application_Start:
ModelBinders.Binders.Add(typeof(AnswerViewModel), new AnswerModelBinder());
You can still use the Html.EditorFor(..), but specify a second parameter which is the name of the editor template. You have a property on the Question object that is the AnswerType, so you could do something like...
#Html.EditorFor(m => m.Answer, #Model.AnswerType)
The in your EditorTemplates folder just define a view for each of the AnswerTypes. ie "String", "DateTime", etc.
EDIT: As far as the Answer object being null for String, i would put a placeholder object there just so the model in you "String" editor template is not null.

Resources