List and Single-Variable in View-Specific Model - asp.net-mvc

I'm creating a page where users can search for a specific school, then view their search results. As such, I think I need a view-specific model that includes both a list of entities as well as a single entity (for the search term). The error I'm encountering is that "DashboardViewModel contains no definition for listschools..."
I've created model as such:
public class DashboardViewModel
{
[Display(Name = "University Name")]
public string name { get; set; }
public List<SchoolViewModel> listschools { get; set; }
}
public class SchoolViewModel
{
public string instnm { get; set; }
}
And I'm trying to accesss both elements in the view as follows, after declaring the model with "#model DashboardViewModel":
<table>
#foreach (var u in Model.listschools)
{
<tr>
<td>
#u.instnm
</td>
</tr>
}
</table>
#using (Html.BeginForm("Index", "Dashboard", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary("", new { #class = "text-danger" })
<div class="row form-group">
<div class="col-md-5">
#Html.LabelFor(m => m.name, new { })
#Html.TextBoxFor(m => m.name, new { #class = "form-control" })

It's usually best to reference the namespace to the ViewModel in the View declaration:
#{
#model Your.Namespace.DashboardViewModel
}
in case you have another ViewModel of the same name being referenced by mistake,
and to make sure the List is populated by the Controller (using the same reference to the ViewModel in question)

Related

MVC BeginCollectionItem helper not binding to model instance within view model

Building a dynamic list in MVC5. Form will post to the controller and I can see the StudentList correctly populated in chrome dev tools but in the controller, the StudentList is empty.
Model:
public class Student
{
public int id { get; set; }
public string Name { get; set; }
}
public class StudentViewModel
{
public Student student { get; set; }
public List<Student> StudentList { get; set; }
}
View:
#model TaskTrack.ViewModels.StudentViewModel
#using (Html.BeginForm("Create", "Timesheet", FormMethod.Post, new { #id = "addTaskForm" }))
{
<div id="task-row">
//Partial view renders here.
</div>
<button class="btn save-day" type="submit" >Save Day</button>
}
Partial view:
#model TaskTracker.ViewModels.StudentViewModel
#using (Html.BeginCollectionItem("StudentList"))
{
<div class="form-row">
<div class="form-group col-2">
#Html.EditorFor(model => model.Student.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Student.Name, "", new { #class = "text-danger" })
</div>
</div>
}
If I take out the Student instance from the ViewModel and replace it with the student model properties, then change the partial view to bind directly to the Name property, it will work.
#Html.EditorFor(model => model.Student.Name, new { htmlAttributes = new { #class = "form-control" } })
to
#Html.EditorFor(model => model.Name, new { htmlAttributes = new { #class = "form-control" } })
Controller post:
[HttpPost]
public ActionResult Create(StudentViewModel model)
When I intercept the post before it hits the controller, I can see it has bound correctly, but in the controller StudentList is empty.
I am trying to avoid having the student model duplicated in the view model because it's duplicate and all the validation rules are already in the Student model, so I would have to duplicate all that in the viewmodel as well, which seems wrong.

How to pass different list of data from view to controller MVC

currently I facing a very tricky problem of passing different list of data from view to controller.
I have created two input box to submit my data to controller so that it can be saved into CreateAccountsDB and further display it in the list of
Selected Subcon when Create button is pressed.
The problem I face here is:
when pressing the Create button with entered data from NewCompanyName textbox and NewEmail textbox, those entered data do pass data from View to Controller and save data into CreateAccountDB (not showing in View), but the entered data is not displaying in the list of Selected Subcon.
Create View
Here is the model.
public class Tender
{
public int ID { get; set; }
public string CompanyName { get; set; }
public List<CreateAccount> FrequentCompanyName { get; set; }
public List<CreateAccount> SuggestCompanyName { get; set; }
public List<CreateAccount> SelectedCompanyName { get; set; }
public string CompanyNameNew { get; set; }
public string EmailNew { get; set; }
public int? TradeID { get; set; }
public virtual Trade Trade { get; set; }
public int? CreateAccountID { get; set; }
public virtual CreateAccount CreateAccount { get; set; }
}
Here is the Get Method of Create function in controller:
[httpGet]
public ActionResult Create(int? id)
{
Tender tender = new Tender();
tender.FrequentCompanyName = db.createaccountDB.Include(tm => tm.Trade).Where(td => td.Frequency == 32).ToList();
tender.SuggestCompanyName = db.createaccountDB.Include(tm => tm.Trade).ToList();
if (tender.SelectedCompanyName == null)
{
tender.SelectedCompanyName = new List<CreateAccount>().ToList();
}
return View(tender);
}
and Here is my Post Method of Create function:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ID,CompanyName,TradeID,FrequentCompanyName,SelectedCompanyName,CreateAccountID")] Tender tender ,string CompanyNameNew, string Emailnew)
{
CreateAccount accnew = new CreateAccount();
accnew.CompanyName = CompanyNameNew;
accnew.Email = Emailnew;
if(ModelState.IsValid)
{
db.createaccountDB.Add(accnew);
db.SaveChanges();
}
if (tender.SelectedCompanyName == null)
{
tender.SelectedCompanyName = new List<CreateAccount>().ToList();
}
tender.FrequentCompanyName = db.createaccountDB.Include(tm => tm.Trade).Where(td => td.Frequency == 32).ToList();
tender.SuggestCompanyName = db.createaccountDB.Include(tm => tm.Trade).ToList();
tender.SelectedCompanyName.ToList().Add(accnew);
return View(tender);
}
and Here is my Create View:
#model Tandelion0.Models.Tender
#{
ViewBag.Title = "Create";
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-group">
#*#Html.LabelFor(model => model.ProjectName, htmlAttributes: new { #class = "control-label col-md-3" })*#
<div class="col-md-3">
<h5>New Company Name</h5>
#Html.EditorFor(model => model.CompanyNameNew, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.CompanyNameNew, "", new { #class = "text-danger" })
</div>
<div class="col-md-3">
<h5>New Email</h5>
#Html.EditorFor(model => model.EmailNew, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.EmailNew, "", new { #class = "text-danger" })
</div>
<div class="container" align="center">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
<div class="container row">
<!--selected subcon column-->
<div class="container row col-sm-4">
<h4>
Selected Subcon
</h4>
<div style="overflow-y: scroll; height:250px;">
<table class="table table-hover">
#foreach (var item in Model.SelectedCompanyName)
{
<tbody>
<tr>
<td>
#Html.DisplayFor(modelItem => item.CompanyName)
</td>
</tr>
</tbody>
}
</table>
</div>
</div>
</div>
}
So far I manage to save data from view into CreateAccountsDB when create button is pressed, but those data just couldn't pass it from Post method Create function to Get method Create function in Controller. The data and the list become null immediate after come out from post method Create function.
Because of data becomes null, the view couldn't receive any data from controller.
May I know how can i solve the the problem of passing data from controller to view? Is the way I pass data totally wrong?
Any advice is truly appreciated.
In your HttpPost Action method :
Instead of :
tender.SelectedCompanyName.ToList().Add(accnew);
You should be doing:
tender.SelectedCompanyName.Add(accnew);
Calling ToList().Add(object) won't actually add to SelectedCompanyName.Instead it will add to the new list object created by calling ToList() method which you are not assigning back to tender.SelectedCompanyName.
A better approach however would be to use Post/Redirect/Get Pattern.
Instead of returning a view from your post method , do a temorary redirect to your [HttpGet]Create action method passing the id of the tender.

complex type model wont pass list propery

hi guys i am having trouble with my mvc app. its a simple quiz app and i am stuck at creating create view for question model.
I have Question and Option model with appropriate view models(in my case they are QustionDTO and OptionDTO) and i want to make cshtml create view for Question with list of Options.like this but when i submit form, my list of options is null.
this is my Question and Option model
public class Question
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public string QuestionText { get; set; }
public virtual ICollection<Option> Options { get; set; }
}
public class Option
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
[Display(Name ="Answer text")]
public string OptionText { get; set; }
[Required]
public bool IsCorrect { get; set; }
}
this is my DTO models
public class QuestionDTO
{
public int Id { get; set; }
public string QuestionText { get; set; }
public List<OptionDTO> Options { get; set; }
}
public class OptionDTO
{
public int Id { get; set; }
public string OptionText { get; set; }
public bool IsCorrect { get; set; }
}
and this is my view with editor template located in "~/views/shared/editortemplate/OptionDTO.cshtml"
#model Quiz.BusinessEntites.QuestionDTO
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>QuestionDTO</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.QuestionText, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.QuestionText, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.QuestionText, "", new { #class = "text-danger" })
</div>
</div>
<table class="table" style="width:50%">
#for (int i = 0; i < 3; i++)
{
#Html.EditorFor(model=>model.Options[i])
}
</table>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
this is OptionDTO editor template
#using Quiz.BusinessEntites
#model Quiz.BusinessEntites.OptionDTO
<tr>
<th class="col-md-2">
#Html.DisplayNameFor(m => m.OptionText)
</th>
<th class="col-md-2">
#Html.DisplayNameFor(m => m.IsCorrect)
</th>
</tr>
<tr>
<td class="col-md-2">
#Html.EditorFor(m => m.OptionText)
</td>
<td class="col-md-2">
#Html.EditorFor(m => m.IsCorrect)
</td>
</tr>
from the image above u can see that options list is null. if u have any suggestion it will be appreciated.
In your http post action method, the Bind attribute with Include list is telling the Model binder to bind only "Id","QuestionText" and "IsCorrect" properties of QuestionDto object from the posted form data. So the model binder will not bind the Options property value.
Remove the Bind attribute from your Http post action method.
There is no need to use the Bind attribute if your view model is specific to your view, means you have only properties needed for your view (In your case it looks like so)
public ActionResult Create(QuestionDTO model)
{
// to do :return something
}
If you want to use a non view specific view model, but still want to use Bind attribute to specify only subset of properties, Include just those properties. In your case, your code will be like
public ActionResult Create([Bind(Include="Id,QuestionText",Options"] QuestionDTO model)
{
// to do :return something
}
Also you should editer template view should be in a directory called EditorTemplates , not EditorTemplate

Data annotation trigger validation on Get method

I have this viewmodel
public class ProductViewModel : BaseViewModel
{
public ProductViewModel()
{
Categories = new List<Categorie>
}
[Required(ErrorMessage = "*")]
public int Code{ get; set; }
[Required(ErrorMessage = "*")]
public string Description{ get; set; }
[Required(ErrorMessage = "*")]
public int CategorieId { get; set; }
public List<Categorie> Categories
}
My controller like this
[HttpGet]
public ActionResult Create(ProductViewModel model)
{
model.Categories = //method to populate the list
return View(model);
}
The problem is, as soon as the view is exhibited, the validation is fired.
Why this is happening?
Thanks in advance for any help.
Update
The view is like this
#using (Html.BeginForm("Create", "Product", FormMethod.Post, new { #class = "form-horizontal", #role = "form" }))
{
<div class="form-group">
<label for="Code" class="col-sm-2 control-label">Code*</label>
<div class="col-sm-2">
#Html.TextBoxFor(x => x.Code, new { #class = "form-control"})
</div>
</div>
<div class="form-group">
<label for="Description" class="col-sm-2 control-label">Desc*</label>
<div class="col-sm-2">
#Html.TextBoxFor(x => x.Description, new { #class = "form-control", maxlength = "50" })
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Categorie*</label>
<div class="col-sm-4">
#Html.DropDownListFor(x => x.CategorieId, Model.Categories, "Choose...", new { #class = "form-control" })
</div>
</div>
You GET method has a parameter for your model, which means that the DefaultModelBinder initializes and instance of the model and sets its properties based on the route values. Since your not passing any values, all the property values are null because they all have the [Required] attribute, validation fails and ModelState errors are added which is why the errors are displayed when the view is first rendered.
You should not use a model as the parameter in a GET method. Apart from the ugly query string it creates, binding will fail for all properties which are complex objects and collections (look at you query string - it includes &Categories=System.Collections.Generic.List<Categorie> which of course fails and property Categories will be the default empty collection). In addition, you could easily exceed the query string limit and throw an exception.
If you need to pass values to the GET method, for example a value for Code, then you method should be
[HttpGet]
public ActionResult Create(int code)
{
ProductViewModel model = new ProductViewModel
{
Code = code,
Categories = //method to populate the list
};
return View(model);
}

Master-Detail created in one View

I have a Model with Child model.
[Table("Personnel")]
public class Personnel
{
[Key]
public int Id { get; set; }
[MaxLength(10)]
public string Code { get; set; }
[MaxLength(20)]
public string Name { get; set; }
public virtual List<PersonnelDegree> Degrees
{
get;
set;
}
}
public class PersonnelDegree
{
[Key]
public int Id { get; set; }
[ForeignKey("Personnel")]
public int PersonnelId { get; set; }
public virtual Personnel Personnel { get; set; }
[UIHint("Enum")]
public Degree Degree { get; set; }
public string Major { get; set; }
public string SubField { get; set; }
public string Location { get; set; }
}
I want to created a view for this.(Add)
I added pesonnel field to view, but how to add items for PersonnelDegree?
#using (Html.BeginForm("Add", "Personnel", FormMethod.Post, new {enctype = "multipart/form-data", #class = "form-horizontal tasi-form", id = "default"}))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, null, new {#class = "alert alert-danger "})
<div class="form-group">
#Html.LabelFor(m => m.Code, new {#class = "control-label col-lg-1"})
<div class="col-lg-3">
#Html.TextBoxFor(m => m.Code, null, new {#class = "form-control", maxlength = 10})
#Html.ValidationMessageFor(m => m.Code)
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.Name, new {#class = "control-label col-lg-1"})
<div class="col-lg-3">
#Html.TextBoxFor(m => m.Name, new {#class = "form-control", maxlength = 20})
#Html.ValidationMessageFor(m => m.Name)
</div>
#Html.LabelFor(m => m.Family, new {#class = "control-label col-lg-1"})
<div class="col-lg-3">
#Html.TextBoxFor(m => m.Family, null, new {#class = "form-control", maxlength = 30})
#Html.ValidationMessageFor(m => m.Family)
</div>
</div>
Can i add multy PersonnelDegrees in this View?
Edit
I add a div in view for Degrees
<div id="Degrees">
<div id="NewDegree" style="display:none">
<div class="form-group">
<input class="form-control" id="Degrees[#].Major" name="Degrees[#].Major" value="" type="text">
// another items
</div>
</div>
</div>
and in javascript :
$(document).ready(function() {
$(function() {
$("#addItem").click(function () {
var index = $('#Degrees tbody tr').length; // assumes rows wont be deleted
var clone = $('#NewDegree').html();
// Update the index of the clone
clone.replace(/\[#\]/g, '[' + index + ']');
clone.replace(/"%"/g, '"' + index + '"');
$('#Degrees').append(clone);
});
);
});
it add a div ,But after a few seconds hide div and refresh page.
Yes, you can. There are several options how to do it:
Use Js grid-like component
Use some js grid component, i prefer jqgrid you can add data localy on your View with it and then serialize it on form POST to controller.
Advantage: You don't need to write js CRUD operations with your grid your self the only thing that you should get is how to serialize local data right way to controller.
Disadvantage: You should learn how component works and could be that some component not easy emplimentable in MVC project (I mean you could lost your model validation, data annotation etc. on client side)
Add markup with js
Write your own js to resolve this issue. Here is good basic example how to do it. The idea that you generate html with js(get js from controller) and add it to your View.
Advantage: You could do what ever you want is you know js.
Disadvantage: You lost your model validation, data annotation etc. on client side.
Use PartialView with js
Get markup from Controller with Ajax (PartialView for your PersonnelDegree) and attach it to your View with js.
Advantage: You can use all ViewModel advandages (DataAnnotations) plus you can add some tricki logic in your CRUD controller methods if you need. Also it's the most easy maintainable solution if your Project is big and have losg life cicle.
Disadvantage: You should learn how to init client side validation when markup comes from ajax call. Also usually this approach force you to write a lot of code.
I prefer last option if i have a time for this.
you can add items for PersonnelDegree using partial views.
for adding multy PersonnelDegrees in this View you need to create objects in the controller
Personnel pers = new Personnel();
PersonnelDegrees pr_obj = new PersonnelDegrees ();
ind_obj.PersonnelDegrees .Add(pr_obj );
PersonnelDegrees pr_obj1 = new PersonnelDegrees ();
ind_obj.PersonnelDegrees .Add(pr_obj1 );

Resources