Handling nested collections with mvc4 view to controller - c#

I've been struggling for a few hours on a concept that I feel should be simple. I have a Model that is essentially a Quiz with a Collection of Questions and that collection has a collection of Answers. Here is a example of my model (simplified):
public class QuizModel
{
public List<Question> Questions { get; set; }
}
public class Question
{
public string TheQuestion { get; set; }
public List<Answer> Answers { get; set; }
}
public class Answer
{
public string Value { get; set; }
}
And the troublesome part, my view:
#using (Html.BeginForm("SubmitQuiz", "Quiz", FormMethod.Post, new { role = "form" }))
{
<ol>
#{
#Html.Hidden("Id", Model.Id, new { Id="pQuizModel"})
for (int vQIndex = 0; vQIndex < Model.Questions.Count; vQIndex++)
{
<li>
#Model.Questions.ElementAt(vQIndex).Question
<ul class="list-unstyled">
#{
for (int vAIndex = 0; vAIndex < Model.Questions.ElementAt(vQIndex).Answers.Count; vAIndex++)
{
<li>#Html.RadioButtonFor(pModel => pModel.Questions.ElementAt(vQIndex).SelectedAnswer, Model.Questions.ElementAt(vQIndex).Answers.ElementAt(vAIndex).Value) #Model.Questions.ElementAt(vQIndex).Answers.ElementAt(vAIndex).Value</li>
//<li>#Html.RadioButton(Model.Questions.ElementAt(vQIndex).Id.ToString() + ":" + Model.Questions.ElementAt(vQIndex).Answers.ElementAt(vAIndex).Id, Model.Questions.ElementAt(vQIndex).Answers.ElementAt(vAIndex).Id)
// #Model.Questions.ElementAt(vQIndex).Answers.ElementAt(vAIndex).Value</li>
}
}
</ul>
</li>
}
}
</ol>
<button type="submit" class="btn btn-default">Submit</button>
}
My controller, just trying to debug it and make sure the Model is filled out properly:
public ActionResult SubmitQuiz(QuizModel pQuizModel)
{
return View();
}
I've literally tried a ton of different suggestions on my view. It's pretty easy to bind directly to a single value in a Model, I can even get my pQuizModel to have the correct Hidden piece you see in the View. But nothing else in the Model gets populated and I can't figure out why.\
Edit: To clarify my problem, the view is good but the controller does not receive any values in the pQuizModel parameter. I don't have the binding setup properly, need some help there.

The problem is not with the Razor rendering of your initial page, it's with the model binder on the resulting POST (after they push the button). The model binder makes certain assumptions about how to map ids into nested items. Because you're not following the convention as you form your Razor code, it isn't filling the method's input properties. See http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx for a similar example.

Related

Get Query Parameter from URL into MVC View

I have an a href link to a page which adds a parameter to the link for example:
tsw/register-your-interest?Course=979
What I am trying to do is to extract the value in Course i.e 979 and display it in the view. When attempting with the below code, I only return with 0 rather than the course value expected. ideally I'd like to avoid using routes.
Here is the view:
<div class="contact" data-component="components/checkout">
#using (Html.BeginUmbracoForm<CourseEnquiryPageSurfaceController>("PostCourseEnquiryForm", FormMethod.Post, new { id = "checkout__form" }))
{
//#Html.ValidationSummary(false)
#Model.Course;
}
And my controller:
public ActionResult CourseEnquiry(string Course)
{
var model = Mapper.Map<CourseEnquiryVM>(CurrentContent);
model.Course = Request.QueryString["Course"];
return model
}
This is the View Model:
public class CourseEnquiryVM : PageContentVM
{
public List<OfficeLocation> OfficeLocations { get; set; }
public string Test { get; set; }
public string Course { get; set; }
public List<Source> SourceTypes { get; set; }
}
SOLUTION:
After some research and comments I've adjusted the code to the below which now retrieves the value as expected
#Html.HiddenFor(m => m.Course, new { Value = #HttpContext.Current.Request.QueryString["Course"]});
Thanks all
Based on the form code you provided you need to use #Html.HiddenFor(m => m.Course) instead of just #Model.Course. #Model.Course just displays the value as text instead of building a input element that will be sent back to your controller.
If your problem is with a link prior to the view you referenced above, here's what I'd expect to work:
View with link:
#model CourseEnquiryVM
#Html.ActionLink("MyLink","CourseEnquiry","CourseController", new {course = #Model.Course}, null)
CourseController:
public ActionResult CourseEnquiry(string course)
{
// course should have a value at this point
}
In your view, you are only displaying the value of Course.. which isn't able to be submitted. You need to incorporate the value of course with a form input element (textbox, checkbox, textarea, hidden, etc.).
I would highly suggest using EditorFor or Textboxfor, but because your controller action is expecting just a string parameter you could just use Editor or TextBox.
#using (Html.BeginUmbracoForm<CourseEnquiryPageSurfaceController>("PostCourseEnquiryForm", FormMethod.Post, new { id = "checkout__form" }))
{
//#Html.ValidationSummary(false)
#Html.TextBox(Model.Course, null, new { #class = "form-control"});
<input type="submit" value="Submit" />
}
Then you should just be able to do this in your controller:
public ActionResult CourseEnquiry(string course) // parameter variables are camel-case
{
var model = Mapper.Map<CourseEnquiryVM>(CurrentContent);
if(!string.IsNullOrWhiteSpace(course))
model.Course = course;
return model;
}
Let me know if this helps.

Remember search params

I'm new in mvc and I try to create a simple page with table and ajax search.
For example, I have a search model, which pass parameters from form to controller.
Model:
public class OrderSearchViewModel
{
[Display(ResourceType = typeof(Lang), Name = "OrderID")]
public int? OrderID { get; set; }
[Display(ResourceType = typeof(Lang), Name = "DeliveryType")]
public int? DeliveryTypeID { get; set; }
[Display(ResourceType = typeof(Lang), Name = "Partner")]
public string CustomerName { get; set; }
public SelectList DeliveryTypes { get; set; }
}
In controller I have an action witch return View with form:
public ActionResult Index()
{
var ordersSearchModel = // default init;
return View(model);
}
In my Index.cshtml I have a form
#model Models.Order.OrderSearchViewModel
<div class="row">
#using (Ajax.BeginForm("Orders", "Order", new AjaxOptions {UpdateTargetId = "ordersList"}, new {#id = "searchForm", #class = "form-horizontal"}))
{
// Editors templates for each params
}
</div>
<div id="ordersList" class="row">
</div>
}
In my Controller I have a method, witch take search model and return a partial view
[HttpPost]
public async Task<ActionResult> Orders(OrderSearchViewModel model, int page = 1, int pageSize = 50)
{
var models = // connect to db and get data filtered by model params
return PartilaView("_View", models);
}
In result partial I have a table with order num and link to edit view.
In edit view I have a link back to search:
#Html.ActionLink("Back", "Index", "Order", new { #class = "btn btn-default" })
And by click this link I get the Index view in default (without search parameters) and user must fill it once again.
What will be the best practice to remember user search parameters?
Thanks for any advice.
As devqon said, the preferred approach is to use query parameters, but it does sound like your usage might make this a bit difficult to manage
TempData/SessionData may solve your issue, but using this approach will cause problems if the user decides to use your application in multiple tabs/windows (they will all share the same search params)
Something that might be worth looking into is SessionStorage.(Link below)
This type of storage persists as long as the browser stays open
And importantly
Opening a page in a new tab or window will cause a new session to be initiated
So your pages shouldn't share state.
More details here:
https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage

count the number of items in view in asp.net mvc

I am very new to asp.net development. In my asp.net mvc project I have model "Employee" and I'm passing a list of "Employee" model to a RAZOR view and I'm trying to count different type of employees and show a summary.
my view is like this,
#{
int available = 0;
int onLeave = 0;
int away = 0;
int unAvailable = 0;
}
#foreach (var employee in Model){
<lable>#employee.Name</lable></br>
#if (#employee.Available){
#available=available+1;
}
#if (#employee.Unavailable){
#unAvailable=unAvailable;
}
#if (#employee.Away){
#away=away+1;
}
#if (#employee.Onleave){
#onLeave=onLeave+1;
}
}
<div>
<!--additional summary is displayed here-->
<label>Available:</label>#available
<label>Unavailable:</label>#unAvailable
<label>Away:</label>#away
<label>On Leave:</label>#onLeave
</div>
but when I run the my project variables "available","unAvailable","away" and "onLeave" don't get updated.
I'm sure that list is not empty because employee names are displaying.
can some explain me what is happening here and correct way of doing this
You should be doing this outside the before passing to the view like I mentioned in my original comment. You can create a new object called a ViewModel to represent the data exactly like you want it on the page. So I created a simple example, I only used the 4 properties of Employee you are displaying in you CSHTML page. On your View where you said your MODEL is either a list, arrary or whatever of Employee change it to EmployeeViewModel. Then in your controller where you get your list of employees set them to the Employees property of the Employee ViewModel.
public class EmployeeViewModel
{
public IEnumerable<Employee> Employees { get; set; }
public int TotalAvailable { get { return Employees.Count(emp => emp.Available); } }
public int TotalUnavailable { get { return Employees.Count(emp => emp.Unavilable); } }
public int TotalAway { get { return Employees.Count(emp => emp.Away); } }
public int TotalOnLeave { get { return Employees.Count(emp => emp.OnLeave); } }
}
public class Employee
{
public bool Available { get; set; }
public bool Unavilable { get; set; }
public bool Away { get; set; }
public bool OnLeave { get; set; }
}
//In the controller do this.
public ActionResult Index() //use your controller Action Name here
{
var employeeViewModel = new EmployeeViewModel { Employees = /*Your list of empoyees you had as a Model before here*/}
return View(employeeViewModel)
}
Change your CSHTML code to something like this:
#foreach(var employee in Model.Employees)
{
<label> #employee.Name </label></br>
}
<div>
<!--additional summary is displayed here-->
<label> Available:</label> #Model.TotalAvailable
<label> Unavailable:</label> #Model.TotalUnavailable
<label> Away:</label> #Model.TotalAway
<label> On Leave:</label> #Model.TotalOnLeave
</div>
An easy and quick way is:
<div>
<!--additional summary is displayed here-->
<label>Available:</label>#Model.Count(i => i.Available)<br>
<label>Unavailable:</label>do the same.
<label>Away:</label>do the same.
<label>On Leave:</label>do the same.
</div>
Make sure the model has already been "ToList()", or it might lead to mult-access of database.
Basically, I only use viewmodel when I need to pass more than 1 models to the view. Not worth in this case.
Make such calculations in View considered a BAD practice.
In your case better option will be create ViewModel with corresponding properties and then pass it to the model, previously calculating count for every type in controller using LINQ. Where you could reference your types like Model.available, Model.away and so on. Using ViewModel it is the best practice for MVC.
#Thorarins answer show you how to use LINQ in your code to calculate count for you types.
UPDATE:
You can use JS, but you should not, because it still not what supposed to happen in View. Work with data should not be handled in View. Don't be scared by ViewModels, they not that hard as it could seem. Please read this article which consider all ways to pass data to View, which has good example how create and pass ViewModel.
Mvc sample on how to do it:
you need a model class
public class EmployeeModel
{
public int Available {get; set;}
public int OnLeave {get; set;}
public int Away {get; set;}
public int UnAvailable {get; set;}
}
and a command:
public ActionResult Index()
{
var model = new EmployeeModel();
model.Available = employee.count(e=> e.available);
model.OnLeave = employee.count(e=> e.onLeave);
model.Away = employee.count(e=> e.away);
model.UnAvailable = employee.count(e=> e.unAvailable );
return View(model);
}
and a view
#model EmployeeModel
<div>
<!--additional summary is displayed here-->
<label>Available:</label>#Model.Available
<label>Unavailable:</label>#Model.UnAvailable
<label>Away:</label>#Model.Away
<label>On Leave:</label>#Model.OnLeave
</div>

Post an Array of Models to Controller

I have an MVC5 project where I am trying to Post a List of 'Permissions' back to the server.
#model List<ConnectConsole.Models.Permissions>
<form action="/products/#{#Html.Raw(Url.RequestContext.RouteData.Values["id"])}/permissions/save" method="POST">
#for (int i = 0; i < Model.Count; i++)
{
#Html.TextBoxFor(model => model[i].ConnectId)
#Html.DropDownListFor(
model => model[i].ScopeVal,
Model[i].ScopeTypes,
"-- Please Select --")
}
<button class="btn btn-full btn-submit">Save</button>
#* removed some html for brevity *#
</form>
The Data that is posted back to the server looks somthing like this (copied from chrome)
[0].ConnectId:88709d85-710c-45da-8fec-0ee661d267b1
[0].ScopeVal:e276964f-7e6e-4aaf-8bf5-cac5b02f3a8f
[1].ConnectId:78709d85-710c-45da-8fec-0ee661d267b1
[1].ScopeVal:
[2].ConnectId:68709d85-710c-45da-8fec-0ee661d267b1
[2].ScopeVal:
My C# model
public class Permissions
{
public String ConnectId { get; set; }
public List<SelectListItem> ScopeTypes;
public String ScopeVal;
}
Method signature that I am posting to
public ActionResult Save(string id, List<Permissions> permissions)
The object seems to be correctly posted to the server with all with all fields in the request but in the method the list that gets serialized from this data only contains the connect ID, it is of the correct size and I get a list that has 3 objects in it but ScopeVal is never set.
Is this a bug in MVC or am I doing something horribly wrong?
The MVC default Model Binder will only bind properties with public getters and setters.
You need to change the field
public String ScopeVal;
to be a property
public String ScopeVal { get; set; }
It's bad C# to expose class fields - this is what properties are for.

Using .net MVC why don't my lists survive a post?

I'm just picking up .net MVC and I've come across something that I can't work out. I'm obviously missing some basic principle but would love some help.
I have a ViewModel with two IEnumerables that I want to use to create dropdownlistfors. My GET works fine, the lists are populated as expected.
Now I'm posting the ViewModel back to a POST method, not to do anything useful but just to try and understand how mvc works. I expected that I would simply be able to re-populate the dropdownlistfors from the model that was posted back - but I get a null reference exception.
Other values, such as partyid, in the ViewModel survive the POST so i'm confused.
I can get it to work if I repopulate the lists but that seems wrong.
Can someone give me a pointer?
My ViewModel
public class DemoViewModel
{
//properties
public IEnumerable<tbl_server_lookup> servers { get; set; }
public int serverId { get; set; }
public IEnumerable<tbl_site_lookup> sites { get; set; }
public int siteId { get; set; }
public int partyid { get; set; }
public string message { get; set; }
public DemoViewModel()
{
}
}
My Controller
// GET: /Demos/Test/
[HttpGet]
public ActionResult Test()
{
DemoViewModel demo = new DemoViewModel();
using (var dbContext = new ADAPI.Models.db_ad_apiEntities2())
{
var serverList = dbContext.tbl_server_lookup.Where(s => s.server_name != null);
demo.servers = serverList.ToList();
var siteList = dbContext.tbl_site_lookup.Where(w => w.site_name != null);
demo.sites = siteList.ToList();
}
demo.message = "Enter the user id you would like to look up in the box below.";
return View(demo);
}
//
//POST: /Demos/Test/
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Test(DemoViewModel demo)
{
//It works if I uncomment this block...
/*using (var dbContext = new ADAPI.Models.db_ad_apiEntities2())
{
var myQuery = dbContext.tbl_server_lookup.Where(s => s.server_name != null);
demo.servers = myQuery.ToList();
var siteList = dbContext.tbl_site_lookup.Where(w => w.site_name != null);
demo.sites = siteList.ToList();
}*/
demo.message = "the user id you posted is: " + demo.partyid + ". The Server you selected is: ";// +demo.serverId;
return View(demo);
}
My View
#model ADAPI.ViewModels.DemoViewModel
<h2>Demos</h2>
<h3>#Model.message</h3>
#using (Html.BeginForm("Test","Demos"))
{
#Html.AntiForgeryToken()
<div class="">
<h4>Party ID</h4>
#Html.ValidationSummary(true)
<!-- input box for party id-->
#Html.TextBoxFor(model => model.partyid)
<!-- dropdown list of server types eg live vs test-->
#Html.DropDownListFor(model => model.serverId, new SelectList(Model.servers, "server_Id","server_name"))
#Html.DropDownListFor(model => model.siteId, new SelectList(Model.sites, "site_short_name","site_name"))
<input type="submit" value="Try" />
</div>
}
The Error
In MVC, model on the views are loaded in the controller action, they are not posted back along with the post action.
If you are used to ASPX's viewstate, there is no such thing in MVC, you need to load what you need for every view in the current action.
Dropdown lists are rendered into html as tag and returned to the server as plain single value.
You have to rebind/repopulate them on the server, wchich is annoying in scenarios like validation, where the same model should be returned to the client.
There is no support for that in the framework - you have to do it on your own.
One more thing - if you absolutely have to return the list items and want them back on the server, you can serialize tham and hide in some hidden field. But it's ugly and unsecure since anyone can change its value.

Categories

Resources