MVC BeginCollectionItem helper not binding to model instance within view model - asp.net-mvc

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.

Related

List and Single-Variable in View-Specific Model

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)

MVC 5 - values entered on form (View) not coming through to the Controller

I created a View for form submission and I expect the form data to be passed to the Controller once the Submit button clicked.
I scaled down the Models and views a little bit for simplicity.
When I Break on the HomeController and execute the step:
ReviewViewModel reviewViewModelx = reviewViewModel;
No values are filled in from the form that I would expect.
View:
#model ReviewViewModel
<div class="panel-body">
#using (Html.BeginForm("SubmitReview", "Home", FormMethod.Post, new { model = Model }))
{
#*#Html.AntiForgeryToken()*#
#Html.ValidationSummary("", new { #class = "text-danger" })
<div class="form-horizontal">
<div class="form-group">
#Html.LabelForRequired(model => model.ReviewModel.Name, htmlAttributes: new { #class = "control-label col-md-5" })
<div class="col-md-5">
#Html.EditorFor(model => model.ReviewModel.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ReviewModel.Name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelForRequired(model => model.ReviewModel.Degree, htmlAttributes: new { #class = "control-label col-md-5" })
<div class="col-md-5">
#Html.EditorFor(model => model.ReviewModel.Degree, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ReviewModel.Degree, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-5">
<input type="submit" value="Submit Review" class="btn btn-default" />
</div>
</div>
</div>
</div>
}
ReviewViewModel This view model contains 3 different data models, I am only showing the Review Class below as that is what is being populated.
public class ReviewViewModel
{
public VideoTypeModel VideoTypeModel;
public VideoModel VideoModel;
public ReviewModel ReviewModel;
public ReviewViewModel()
{
VideoTypeModel = new VideoTypeModel();
VideoModel = new VideoModel();
ReviewModel = new ReviewModel();
}
public ReviewViewModel(VideoTypeModel videoTypeModel, VideoModel videoModel, ReviewModel reviewModel)
{
this.VideoTypeModel = videoTypeModel;
this.VideoModel = videoModel;
if (reviewModel != null)
{
this.ReviewModel = reviewModel;
}
}
}
Review Data Model:
ReviewModel
public class ReviewModel
{
[Required]
[Display(Name="Name: ")]
public string Name { get; set; }
[Required]
[Display(Name="Degree:")]
public string Degree { get; set; }
}
HomeController
As mentioned earlier, after first step, there is no values in the reviewViewModelx.ReviewModel.Name or
reviewViewModelx.ReviewModel.Email (or any other fields that are in the model as I removed in this sample for brevity.
[HttpPost]
public ActionResult SubmitReview(ReviewViewModel reviewViewModel)
{
ReviewViewModel reviewViewModelx = reviewViewModel;
SendReviewEmail ce = new SendReviewEmail(null);
SendReviewEmailToTech ste = new SendReviewEmailToTech(null);
return View(reviewViewModel);
}
I'm somewhat of a newbie to MVC, any assistance is appreciated. Thanks in advance!
Edit: It appears the issue may be in your ReviewViewModel. I don't have an MVC project in front me, but I think you may be missing your getters and setters for the objects. Can you try something like:
public class ReviewViewModel
{
public VideoTypeModel VideoTypeModel {get; set; }
public VideoModel VideoModel {get; set; }
public ReviewModel ReviewModel {get; set; }
}
In your controller you need to add the Post tag. Typically you will have one action for the HttpGet and on for the HttpPost
public ActionResult SubmitReview()
{
return View();
}
[HttpPost]
public ActionResult SubmitReview(ReviewViewModel reviewViewModel)
{
ReviewViewModel reviewViewModelx = reviewViewModel;
SendReviewEmail ce = new SendReviewEmail(null);
SendReviewEmailToTech ste = new SendReviewEmailToTech(null);
return View(reviewViewModel);
}

Model binding doesn't work for complex object

Here's the view I'm going to post:
#model WelcomeViewModel
#using (Html.BeginForm("SignUp", "Member", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post))
{
....
<div class="form-group">
#Html.EditorFor(model => model.SignUp.CompanyName, new {htmlAttributes = new {#class = "form-control" }})
</div>
<div class="form-group">
#Html.EditorFor(model => model.SignUp.RegisteredNo, new {htmlAttributes = new {#class = "form-control" } })
</div>
....
<button type="submit" name="signup" class="btn">Register</button>
}
ViewModel:
public class WelcomeViewModel
{
public SignInViewModel LogOn { get; set; }
public SignUpViewModel SignUp { get; set; }
}
Action method:
[HttpPost, AllowAnonymous, ValidateAntiForgeryToken]
public virtual async Task<ActionResult> SignUp(SignUpViewModel model)
{
if (!ModelState.IsValid)
return View("SignIn", new WelcomeViewModel { SignUp = model });
// other code
return View();
}
When I post the data, the model gets null. I know the inputs will be generated like:
<input id="SignUp_CompanyName" name="SignUp.CompanyName">
But the model binder accepts this:
<input id="SignUp_CompanyName" name="CompanyName">
Now I want to know how can I remove that prefix? I know I can explicitly add name for each input:
#Html.TextBoxFor(model => model.SignUp.CompanyName, new { Name = "CompanyName" })
but I want to do it in a strongly type way.
Perhaps the easiest way would be to apply the [Bind] attribute with its Prefix set to "SignUp":
public async Task<ActionResult> SignUp([Bind(Prefix="SignUp")] SignUpViewModel model)
See MSDN

MVC5 problems with DropDownList and view model

So I have a simple database table in the form of ID, EmployeeID, date etc. Which creates a normal model:
public partial class WorkItem
{
public int ID { get; set; }
public int EmployeeID { get; set; }
public int LocationID { get; set; }
[DataType( DataType.Date )]
[Display( Name = "Start date" )]
public System.DateTime StartDate { get; set; }
My problem occurs when I need to augment the functionality of this model and so I create a view model to group work items on a weekly basis.
public class WeeklyWorkItemsViewModel
{
public WorkItem WorkItemMonday { get; set; }
public WorkItem WorkItemTuesday { get; set; }
All works perfectly well for the DateTime field in my view (which is bound to the view model):
<div class="form-group">
#Html.LabelFor( model => model.WorkItemMonday.StartDate, "Week start date", htmlAttributes: new { #class = "control-label col-md-2" } )
<div class="col-md-10">
#Html.EditorFor( model => model.WorkItemMonday.StartDate, new { htmlAttributes = new { #class = "form-control" } } )
#Html.ValidationMessageFor( model => model.WorkItemMonday.StartDate, "", new { #class = "text-danger" } )
</div>
</div>
The problem occurs trying to bind the dropdownlilst, it gets populated correctly but the changes are not seen in the controller.
<div class="form-group">
#Html.LabelFor( model => model.WorkItemMonday.EmployeeID, "EmployeeID", htmlAttributes: new { #class = "control-label col-md-2" } )
<div class="col-md-10">
#Html.Hidden( "selectedEmployee" )
#Html.DropDownList( "EmployeeID", null, htmlAttributes: new { #class = "form-control" } )
#Html.ValidationMessageFor( model => model.WorkItemMonday.EmployeeID, "", new { #class = "text-danger" } )
</div>
</div>
The StartDate is updated in the controller.
After mucho head scratching, I finally had to get around this using:
#Html.Hidden( "selectedEmployee" )
And updating this in JQuery. I did try using #html.DropDownListFor but no joy so far.
Can anyone see what's wrong before I pull ALL my hair out.
You model does not contain a property named EmployeeID. But it does have ones named WorkItemMonday.EmployeeID and WorkItemTuesday.EmployeeID.
Stop using DropDownList() and use the strongly typed DropDownListFor() method so that you correctly bind to your model properties.
Modify you view model to include a property for the SelectList
public IEnumerable<SelectListItem> EmployeeList { get; set; }
and populate it in the GET method before you pass the model to the view. Then in the view use
#Html.DropDownListFor(m => m.WorkItemMonday.EmployeeID, Model.EmployeeList, new { #class = "form-control" })
....
#Html.DropDownListFor(m => m.WorkItemTuesday.EmployeeID, Model.EmployeeList, new { #class = "form-control" })
which will correct generate the name="WorkItemMonday.EmployeeID" and name="WorkItemTuesday.EmployeeID" attributes so that they will bind to your model when you post.

EditUserViewModel needs a DropDownListFor()

Right now I have added a Region to the ApplicationUser model in Identity 2.0
On the UsersAdmin view, Edit action, I have the following stock code to display/edit the Region of the User:
<div class="form-group">
#Html.LabelFor(model => model.Region, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.Region, new { #class = "form-control" })
</div>
</div>
How do I make that TextBox into a DropDownList that allows the user to choose from a list of Region names where Regions is part of ApplicationDbContext?
public class Region
{
[Key]
public Guid ID { get; set; }
public string Name { get; set; }
public Company Company { get; set; }
public Region()
{
this.ID = Guid.NewGuid();
}
}
You could use a view model. In order to render a dropdown you need 2 properties in your view model: a scalar property to hold the selected value and a collection property to represent the list of possible values to be displayed:
public class MyViewModel
{
public Guid SelectedRegionID { get; set; }
public IEnumerable<SelectListItem> Regions { get; set; }
}
That your controller action will populate and pass to the view:
public ActionResult Index()
{
var viewModel = new MyViewModel();
viewModel.Regions = db.Regions.ToList().Select(x => new SelectListItem
{
Value = x.ID.ToString(),
Text = x.Name,
});
return View(viewModel);
}
and in the corresponding strongly typed view you could use the DropDownListFor helper:
#model MyViewModel
<div class="form-group">
#Html.LabelFor(model => model.Region, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(m => m.SelectedRegionID, Model.Regions, new { #class = "form-control" })
</div>
</div>

Resources