I'm trying to get data from 2 table using Linq , that one table had FK to the second table but not necessary has data (table review could have for each review comments (many)) what i'm trying to get is: in a single view get all the reviews and if there are any comments display them related to the review Id
trying to use join get me error in my view (model pass is wrong i tried each table model) this is my code :
public ActionResult ttt()
{
var model = from rev in db.reviews
join com in db.Comments
on rev.ReviewId equals com.ReviewId into JoineRevCom
from com in JoineRevCom.DefaultIfEmpty()
select new
{
rev.ReviewBody,
rev.ReviewHeadLine,
Comments = com != null ? com.CommentBody : null
};
return View(model);
}
#model IEnumerable< SiteMvcPro.Models.Review>
As always I would start by writing a view model for this view containing the information that I would like to display and never send anonymous objects to your view like you did in your code.
Let's suppose that you want to display a list of reviews and for each review the list of corresponding comments. So your view model might look something along those lines:
public class ReviewViewModel
{
public int ReviewId { get; set; }
public string ReviewBody { get; set; }
public string ReviewHeadLine { get; set; }
public IList<CommentViewModel> Comments { get; set; }
}
public class CommentViewModel
{
public string CommentBody { get; set; }
}
with this defined you could perform your LINQ query to extract the necessary data and project to this view model:
IEnumerable<ReviewViewModel> viewModel =
from review in db.reviews
join comment in db.Comments
on review.ReviewId equals comment.ReviewId into joinedReviewComment
select new ReviewViewModel // <-- Always project to a view model and never to an anonymous object
{
review.ReviewBody,
review.ReviewHeadLine,
Comments = joinedReviewComment.Select(c => new CommentViewModel
{
CommentBody = c.CommentBody,
}).ToList(),
};
return View(viewModel.ToList()); // <-- Always pass a view model to your view
And now all that's left is to display this information in your strongly typed view:
#model IList<ReviewViewModel>
<table>
<thead>
<tr>
<th>Review id</th>
<th>Review body</th>
<th>Review headline</th>
<th>Review comments</th>
</tr>
</thead>
<tbody>
#for (var i = 0; i < Model.Count; i++)
{
<tr>
<td>#Html.DisplayFor(x => x[i].ReviewId)</td>
<td>#Html.DisplayFor(x => x[i].ReviewBody)</td>
<td>#Html.DisplayFor(x => x[i].ReviewHeadLine)</td>
<td>
#for (var j = 0; j < Model[i].Comments.Count; j++)
{
<div>
#Html.DisplayFor(x => x[i].Comments[j].CommentBody)
</div>
}
</td>
</tr>
}
</tbody>
</table>
This being said projecting is one thing but filtering your data is another. Suppose that you have millions of reviews and each review has millions of comments. Making the aforementioned query will simply bring your server down pretty quickly. So think about that when designing your application and views. Don't hesitate to use the Where, Skip and Take operators to filter your result-sets down into a meaningful collection of data that is reasonable enough to be displayed on a single view.
Related
new MVC programmer here. Really enjoying it so far, but I have been caught on a snag and I am hoping to get pointed in the right direction.
I have two tables in my Data Connections:
Guests
- Id (Key)
Name
partyId
Party
- Id (Key)
Name
isRSVP
Currently, I am returning the data from the Party table via a ViewModel into a View that displays a table which iterates through the rows of Party and displays the information for each row.
What I would like to do is get a COUNT of the rows where Party.Id = Guest.partyId, and return the count to that same view. So if Party A had three Guest and Party B had two guest, that would be reflected in my View.
Thanks!
Edited with code snippets:
Controller Index Method -
// GET: Parties
public ActionResult Index()
{
var viewModel = new PartyViewModel
{
Parties = _context.Parties.ToList(),
Guests = _context.Guests.ToList()
};
return View("Index",viewModel);
}
PartyViewModel -
public class PartyViewModel
{
public IEnumerable<Party> Parties { get; set; }
public IEnumerable<Guest> Guests { get; set; }
public int guestCount { get; set; }
}
Table from Index view that I am populating:
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>Party's Name</th>
<th>Party Size</th>
<th>RSVP</th>
</tr>
</thead>
<tbody>
#foreach (var party in Model.Parties)
{
<tr>
<td>#Html.ActionLink(party.Name, "EditPartyStatus", "Parties", new { id = party.Id }, null)</td>
<td>INSERT COUNT HERE</td>
<td>#party.isRSVP</td>
</tr>
}
</tbody>
</table>
Screenshot of what im trying to do:
http://imgur.com/a/JCpqv
Where you have "INSERT COUNT HERE" in your view just add the following line:
#Model.Guests.Count(g => g.PartyId == party.Id)
I'm working on an internet lesson plans application for a project. The lesson plan is built from following models (generated with Entity Framework in Database First approach):
public partial class Subject
{
public int Id { get; set; }
public string Hour { get; set; }
public string Name { get; set; }
public int DayId { get; set; }
[Required]
public int PlanId { get; set; }
public virtual Day Day { get; set; }
public virtual Plan Plan { get; set; }
}
public partial class Plan
{
public Plan()
{
this.Przedmiot = new HashSet<Subjects>();
}
public int Id { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<Subject> Subject { get; set; }
}
I have no problems with displaying both models in one View, but I can't seem to figure out how to post both models to database when creating new plan. I want my View to look something like this:
So my question is what's the best approach here, and how can I create one record in Plan table in database, and many Subject records linked to it in this one view.
Edit:
Code with my display View as requested (omitted unnecessary parts because it's rather long):
#model IEnumerable<Lesson_plan.DAL.Subject>
<table style="border: 1px solid black; margin: 40px; width: 100%;">
<tr>
<th>Hours</th>
<th>Monday</th>
<th>Tuesday</th>
<th>Wednesday</th>
<th>Thursday</th>
<th>Friday</th>
<th>Saturday</th>
<th>Sunday</th>
</tr>
#{
if (Model != null)
{
var r = 1;
var t = 1;
List<string> hours = new List<string>();
foreach (var subject in Model)
{
if (!hours.Contains(subject.Hour))
{
<tr>
<td>
<textarea>
#Html.DisplayFor(modelItem => subjest.Hour)
#{ hours.Add(subject.Hour); }
</textarea>
</td>
<td>
<textarea>
#foreach (var subjectName in Model)
{
if (subjectName.Day.DayOfTheWeek.Equals("Monday") &&
subject.Hour.Equals(subjetName.Hour))
{
#Html.DisplayFor(modelItem => subject.Name)
}
}
</textarea>
</td>
//and so on for every day
}
</tr>
r++;
}
}
}
}
</table>
Code of my Controller class (I did some experiments with Create method, but I'm posting original method here):
namespace Lesson_plan.Controllers
{
public class PlansController : Controller
{
private readonly LessonPlanEntities db = new LessonPlanEntities();
// GET: Plans
public ActionResult Index()
{
var plans = db.Plan.ToList();
return View(plans);
}
// GET: Plans/Details/5
public ActionResult Details(int? id)
{
if (id == null)
return Create();
var subjects = db.Subject.
Where(x => x.PlanId == id).
OrderByDescending(x => x.Hour).ToList();
if (subjects.Count > 0)
ViewBag.Title = subjects[0].Plan.Name;
return View(subjects);
}
// GET: Plans/Create
public ActionResult Create()
{
return View();
}
[HttpPost]
public ActionResult Create(Plan plan)
{
if (ModelState.IsValid)
{
db.Plan.Add(plan);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(plan);
}
// GET: Plans/Edit/5
public ActionResult Edit(int? id)
{
if (id == null)
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
var plan = db.Plan.Find(id);
if (plan == null)
return HttpNotFound();
return View(plan);
}
// POST: Plans/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "Id,Nazwa")] Plan plan)
{
if (ModelState.IsValid)
{
db.Entry(plan).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(plan);
}
// GET: Plans/Delete/5
public ActionResult Delete(int? id)
{
if (id == null)
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
var plan = db.Plan.Find(id);
if (plan == null)
return HttpNotFound();
return View(plan);
}
// POST: Plans/Delete/5
[HttpPost]
[ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
var plan = db.Plan.Find(id);
db.Plan.Remove(plan);
db.SaveChanges();
return RedirectToAction("Index");
}
protected override void Dispose(bool disposing)
{
if (disposing)
db.Dispose();
base.Dispose(disposing);
}
}
}
Edit2
Code for the Create View with form:
#model Lesson_plan.DAL.Plan
#using (Html.BeginForm("Create", "Plans"))
{
<div>
#Html.LabelFor(plan => plan.Name)<br/>
#Html.TextAreaFor(plan => plan.Name)
</div>
<table style="border: 1px solid black; margin: 40px; width: 100%;">
<tr>
<th>Hours</th>
<th>Monday</th>
<th>Tuesday</th>
<th>Wednesday</th>
<th>Thursday</th>
<th>Friday</th>
<th>Saturday</th>
<th>Sunday</th>
</tr>
#{
for (int i = 0; i < 7; i++)
{
<tr>
//what sould i put here ?
<td><textarea></textarea></td>
<th><textarea></textarea></th>
<th><textarea></textarea></th>
<th><textarea></textarea></th>
<th><textarea></textarea></th>
<th><textarea></textarea></th>
<th><textarea></textarea></th>
<th><textarea></textarea></th>
</tr>
}
}
</table>
<p>
<a type="button" class="btn btn-info" href=#Url.Action("Index")>Back</a>
<input type="submit" value="Create"/>
</p>
}
The best approach is to design a ViewModel class that can hold all the data on your Form that needs to be submitted, for both Plan(s) and/or Subject(s).
In the Action Method that receives this ViewModel you can pick apart the data, and based on that do whatever inserts and/or updates are needed to store it in the database.
Only the Controller should have knowledge of your database classes. All mapping between ViewModel and Database objects should be done by the controller, and the View should not be bothered or hampered by this.
Multiple models can be sent to the view by combining them into a viewmodel. However, your issues seem to be different, so here goes the long answer:
As for the DB design question, I'm assuming that you're using a relational storage and one subject can be present in multiple plans so a many-to-many should be in place. You can read more on the subject here.
As for your web app, you can usually simply create a ViewModel, that contains both your desired models. If you hadn't elaborated on your question, that would be the correct answer. However, in your case, your two "model" classes are already linked, so your model can simply be a Plan.
Since I'm assuming you're using EF6 (based on your generated classes) your subjects for the plan will be posted back alongside the plan.
What I'd do is cache the currently existing subjects somewhere in your app using a singleton, and then on postback you should iterate through the cached subjects for your plan, checking if they already exist in your DB. If they don't - insert them first, if they do - simply add them to your ICollection in the plan. Then you should probably know how to insert the Plan using EF.
There are probably going to be much more bumps along the way, but you can raise new questions about those - your question as it is now is way too broad.
I'm trying to help you get something working, although it's probably not going to be ideal. Once you get it to work, I'd suggest reading on where to put business logic (and how to keep your controller small). This article could be a good start.
Another thing I'd suggest is to keep your data access in a separate layer. Goodle "3-layer architecture" and also check this video on the repository pattern.
Good luck!
** Post OP-edit **
Ok, looking at your code there seem to be a lot of issues, that you'll probably want to separate into multiple questions. Here's a start:
There is no form on your View and you need a form and a post button on your View, and a version of your Action method on the Controller marked with [Post]. I recommend this tutorial for asp.mvc core and this for older versions. If you want different pages for your "edit" and "display" views, simply create separate ones, but when you're creating, you'll want to have a form.
For the time being, you should use a Plan as a viewModel. Typically this is not ideal, but to explain why I'd need to write a book as an answer. The resources linked should help you understand that part.
You seem to be using an older version of asp.net mvc (maybe I'm wrong? please tell us which one). If you want to ADD new Subjects on specific dates on the client, you'll also have to use javascript (jQuery) - or a full client-side library such as React or Angular. If you're not familiar with full frameworks stick to JQuery for now. Examine this question and answer to get an idea of how to achieve that.
You might want to separate your API methods and your plain old MVC methods into different projects.
** Edit 2 **
Seeing your Plan creation code now, you seem to have correctly read that #Html.TextAreaFor(plan => plan.Name) is the way to create an input that can be posted to the server.
However you're only using it for your plan name.
Instead of using textarea below (I'm assuming these are for the Subject names) use something like
#for(int i = 0; i < plan.Subject.Count(); i++)
{
#Html.TextAreaFor(plan => plan.Subject[i].Name)
}
Assuming the Plan you passed as a model to the view has any subjects, this will be the way to list them and make them editable.
Now, it seems that you want to separate them based on the day of the week, so you might want to do some filtering, for example:
var monday = 1;
#for(int i = 0; i < plan.Subject.Where(s => s.DayId == monday).OrderBy(s => s.Hour).Count(); i++)
And paste this into each of the seven columns. This will now create textareas for every subject that already exists in there.
As for dynamically creating new ones on the view, refer to point #3 above. If you don't have any subjects yet, create some fake ones in your db, just so you can render some on the client and check their source code, then follow the article I linked in #3.
I have been working on an MVC ViewModel that contains scalar data values I need to pass to the View (instead of using ViewBags) along with an IEnumerable that contains the tabular data that goes in the table. This was based on a suggestion someone made on an earlier SO question. This way, there is no duplication I would have if I added the scalar values as constants to a table that could have many, many rows.
I am pasting my (simplified) data model and view model definitions and the controller action in question. The data structures are what I need it to be, but Visual Studio won't create a View from this Action (right-click and select "Add View with Model").
My question, is given the Action and Model definitions, what do I need to do so that I can create a View of the View Model?
I'm figuring auto-scaffold is out of the question because the ViewModel has no key, so I'm expecting manual coding. I have no idea how to do this, because all the examples I've seen show ViewModels that are essentially the equivalent of merging two tables into a larger table.
I have tried using #model = [ViewModelName] and #model = IEnumerable, but both failed.
The binary response would be, "Duh, the error is telling you exactly what's going on. You don't have a Key." But that response doesn't help me resolve this. How do I move forward with this definition of the ViewModel and create a View?
View Model
public class StudentRosterViewModel
{
// This list is the "table" shown on the view
public IEnumerable<StudentRoster> StudentRosters { get; set; }
// These are scalar values apply to the view as a whole
public string SelectedCampus { get; set; }
public string SelectedFiscalYear { get; set; }
}
Data Model
[Table("StudentRoster")]
public partial class StudentRoster
{
public int ID { get; set; }
[Required]
[StringLength(3)]
public string Campus { get; set; }
[Required]
[StringLength(4)]
public string FiscalYear { get; set; }
[Required]
[StringLength(50)]
public string StudentName { get; set; }
public int StudentID { get; set; }
}
Controller Action:
public ActionResult FilterableIndex(string campus="MRA",string fiscalYear = "FY16")
{
StudentRosterViewModel vm = new StudentRosterViewModel();
// this is tabular data and goes in the "table"
vm.StudentRosters = db.StudentRosters
.Where(m => m.Campus == campus)
.Where(m => m.FiscalYear == fiscalYear)
.ToList();
// These are scalar values; they are not tabular
// These are needed to supply values to jQuery
vm.SelectedCampus = campus;
vm.SelectedFiscalYear = fiscalYear;
return View(vm);
}
The convention is that in your Views folder there's a folder corresponding to the name of your controller. For example, if the controller is StudentRosterController then the folder would be named StudentRoster.
Then the view would correspond to the name of the action - FilterableIndex.cshtml.
The .cshtml file would begin with
#model MvcApp.Controllers.StudentRosterViewModel
(Where MvcApp.Controllers is the namespace that contains your model.)
Thanks to the help of everyone in the comments (#StephenMuecke, #LinhTuan) and #ScottHannen, I am able to create a working and improved View page of the ViewModel.
Here are the structural improvements I made.
I created a ViewModels folder and placed the ViewModel class there (no longer in the Models folder).
I took out the [Key] attribute and Id property from the ViewModel -- it really wasn't what made it appear to work before.
I right-clicked the Action Name and selected "Create View" (empty without model)
The View was successfully created. The View code you see below is a basic test for ability access the ViewModel properties and display data -- I can add Bootstrap styles and the functionality, now that I have a basic test.
The IEnumerable took a little work to unpack. I later learned how to add a variable var headerMeta = Model.StudentRosters.FirstOrDefault(); so that I could use #Html.DisplayNameFor in the table body section, I picked up another trick (modelItem => item.[column name]).
A potential further step is to create a custom DisplayFor and DisplayNameFor helper so that I can more directly use the Html helpers without having to add the headerMeta variable.
I call this an initial success because I finally have data reaching the view.
#model ViewModelTest.Controllers.ViewModels.StudentRosterViewModel
#{
ViewBag.Title = "FilterableIndex";
#*This enables us to read the StudentRoster column names*#
var headerMeta = Model.StudentRosters.FirstOrDefault();
}
<h2>FilterableIndex</h2>
<p>Test for reception of data</p>
<ul>
<li>Selected campus: #Model.SelectedCampus</li>
<li>Selected fiscal year: #Model.SelectedFiscalYear</li>
</ul>
#*I'll add bootstrap styles later*#
<table>
<tr>
<th>#Html.DisplayNameFor(m => headerMeta.Campus)</th>
<th>#Html.DisplayNameFor(m => headerMeta.FiscalYear)</th>
<th>#Html.DisplayNameFor(m => headerMeta.StudentName)</th>
<th>#Html.DisplayNameFor(m => headerMeta.StudentID)</th>
</tr>
#foreach (var item in Model.StudentRosters)
{
<tr>
<td>#Html.DisplayFor(modelItem => item.Campus)</td>
<td>#Html.DisplayFor(modelItem => item.FiscalYear)</td>
<td>#Html.DisplayFor(modelItem => item.StudentName)</td>
<td>#Html.DisplayFor(modelItem => item.StudentID)</td>
</tr>
}
I'm trying to print out data from an SQL query, but I'm running into error after error.
Bassically what Im trying to do is fetch some data from teachers out of a database, and then make a list of them on my view. However everytime I try to print, I get this error
ViewModel' does not contain a definition for 'FirstName' and no
extension method 'FirstName' accepting a first argument of type
'ViewModel' could be found.
Here's my code;
ViewModel
namespace Ability.Models
{
public class ViewModel
{
public virtual IEnumerable<Teacher> Teachers { get; set; }
public Teacher Teacher { get; set; }
public virtual IEnumerable<Skill> Skills { get; set; }
public Skill Skill { get; set; }
}
}
Controller
public ActionResult Index()
{
string campus = Request.Params["Campus"];
if (campus != null)
{
if (campus == "Alle")
{
var teachers = (from t in db.Teachers
select t);
}
else
{
var teachers = (from t in db.Teachers
where t.Campus == campus
select t );
}
}
else
{
var teachers = (from t in db.Teachers
select t);
}
var Model = new ViewModel
{
Teachers = db.Teachers.ToList(),
};
return View(Model);
}
View
#model IEnumerable<Ability.Models.ViewModel>
<h2>Teachers at Thomas More</h2>
...
#foreach (var Teacher in Model)
{
<tr>
<td>
#Teacher.FirstName
</td>
<td>
#Teacher.LastName
</td>
<td>
#Teacher.Email
</td>
<td>
#Teacher.Campus
</td>
</tr>
}
I'm not sure if it mathers, but I already tried doing several things including changing my loop to
#foreach (var Teacher in Model.Teachers)
but then I receive a similar error
'IEnumerable' does not contain a definition for 'Teachers'
and no extension method 'Teachers' accepting a first argument of type
'IEnumerable' could be found
Looking forward to any help I can receive!
I see a couple of things that doesn't make sense.
First of all, where do you use the teachers?
Personally, I think that the following code is more reasonable:
public ActionResult Index()
{
IEnumerable<Teacher> teachers = Enumerable.Empty<Teacher>();
string campus = Request.Params["Campus"];
if (campus != null)
{
if (campus == "Alle")
{
teachers = (from t in db.Teachers
select t);
}
else
{
teachers = (from t in db.Teachers
where t.Campus == campus
select t );
}
}
else
{
teachers = (from t in db.Teachers
select t);
}
var Model = new ViewModel
{
Teachers = teachers
};
return View(Model);
}
Second, you have to initialize also the other properties of ViewModel in the above action method.
Now let's go to the view. You pass there a model of type ViewModel. When you want to iterate through the teachers sequence, you can try this:
#foreach (var teacher in Model.Teachers)
{
<tr>
<td>
#teacher.FirstName
</td>
<td>
#teacher.LastName
</td>
<td>
#teacher.Email
</td>
<td>
#teacher.Campus
</td>
</tr>
}
All the above makes sense provided that db.Teachers is an IEnumerable<Teacher>.
You're sending the type ViewModel to the view:
var Model = new ViewModel
{
Teachers = db.Teachers.ToList(),
};
return View(Model);
But it's expecting the type IEnumerable<Ability.Models.ViewModel>:
#model IEnumerable<Ability.Models.ViewModel>
That is, it's expecting a collection where you're providing an object.
Given the usage in the view, it seems like you should just change it to expect a single object:
#model Ability.Models.ViewModel
This would allow you to directly access the property you're looking for:
#foreach (var Teacher in Model.Teachers)
Side Note: The logic in your controller doesn't look correct. You conditionally create a teachers variable, but then never use it. Instead, you just materialize all of the teachers in the database and send them all to the view. If you intend to see all of the teachers, you can remove most of that code. If you intend to see specific teachers, you would want to use the variable you created. Reference #Christos' answer for a practical example of that.
Your model is a list of ViewModels. You needed to use #foreach (var Teacher in Model.Teachers).
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.