MVC how to update related navigation property - c#

EDIT reformed my question based on #Stephen Muecke answer ..
it took me months trying to resolve such problem, but failed to.
my entities are:
public partial class Book
{
public int bookID { get; set; }
public string name { get; set; }
public string chapter { get; set; }
public virtual ICollection<Root> roots { get; set; }
}
.
public class Root
{
public int rootID { get; set; }
public string rooting { get; set; }
public virtual ICollection<Book> Book{ get; set; }
}
BooksController:
public class BooksController : Controller
{
private Context db = new Context();
// GET: Books
public ActionResult Index()
{
var boo = db.books.Include(x => x.roots).ToList();
List<RootyVM> model = new List<RootyVM>();
return View(boo);
}
....
....
// GET: Books/Create
public ActionResult Create()
{
return View();
}
// POST: Books/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(IEnumerable<RootyVM> rootings, Book book)
{
if (ModelState.IsValid)
{
db.books.Add(book);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(book);
}
my view model RootyVM:
public class RootyVM
{
public int? rootID { get; set; }
[Required(ErrorMessage = "Please enter the name of the root!!")]
public string rooting { get; set; }
}
and my partial view _Rooting.cshtml
#model project.ViewModels.RootyVM
<div class="rooting">
#using (Html.BeginCollectionItem("rooting"))
{
#Html.HiddenFor(m => m.rootID, new { #class = "rootID" })
#Html.LabelFor(m => m.rooting)
#Html.TextBoxFor(m => m.rooting)
<button type="button" class="delete">Delete</button>
}
</div>
and my Razor view (Create.cshtml) as follows:
#model project.Models.Book
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Book</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.chapter, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.chapter, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.chapter, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#using (Html.BeginForm())
{
<div id="rootings">
foreach(var rooting in Model)
{
#Html.Partial("_Rooting", rooting)
}
</div>
<button id="add" type="button">Add</button>
<input type="submit" value="Save" />
}
</div>
<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>
<script type="text/javascript">
var url = '#Url.Action("Rooting")';
var form = $('form');
var rootings = $('#rootings');
$('#add').click(function() {
$.get(url, function(response) {
rootings.append(response);
// Reparse the validator for client side validation
form.data('validator', null);
$.validator.unobtrusive.parse(form);
});
});
$('.delete').click(function() {
var container = $(this).closest('.rooting');
var id = container.find('.id').val();
if (id) {
// make ajax post to delete item
$.post(yourDeleteUrl, { id: id }, function(result) {
container.remove();
}.fail(function (result) {
// Oops, something went wrong (display error message?)
}
} else {
// It never existed, so just remove the container
container.remove();
}
});
</script>
}
HOWEVER, there is a mistake where i cannot find. Will appreciate your patience and help
ORIGINAL MAIN REQUESTS
however, I'm struggling in Create and Edit method. What I need to do is that:
Create a new book record and assign a root or more in the same view.
using dropdownlist is preferable.
if root doesn't exist, i want to add/create it on the fly, I mean immediately in the same create view.
I would appreciate if the Create view for relationship property i.e. Root is based on java where token are used (Select2).
I hope I made it clear for you to help me.

Related

Update ViewModel after dynamically updating the view

I am trying to define ViewModels that faithfully represent the view (to make strict use of that concept).
Some of the elements of the ViewModel are updated dynamically. The problem I have, is that when I do the Post, the ViewModel returns without the elements that were updated dynamically.
The update is done through jQuery, when an event is performed. An action is invoked through Url.Action, and a Div is updated.
I made an example to clarify the scenario. An application that only stores a location (state and city). For this I have three ViewModels: one to represent the States in a SelectList, one to represent the Cities in a SelectList, and finally one to represent the Location (formed by the two ViewModel that I mentioned first).
Models:
public class State
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
}
public class City
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public int StateId { get; set; }
public virtual State State { get; set; }
}
ViewModels:
public class CitySelectListViewModel
{
public CitySelectListViewModel() { }
public CitySelectListViewModel(IEnumerable<Models.City> cities)
{
this.Cities = cities;
}
[Display(Name = "Cities")]
[Required]
public int? SelectedCityId { get; set; }
public IEnumerable<City> Cities { get; }
}
public class StateSelectListViewModel
{
public StateSelectListViewModel() { }
public StateSelectListViewModel(IEnumerable<State> states)
{
this.States = states;
}
[Display(Name = "States")]
[Required]
public int? SelectedStateId { get; set; }
public IEnumerable<State> States { get; }
}
public class LocationCreateViewModel
{
public LocationCreateViewModel() { }
public LocationCreateViewModel(ICollection<State> states)
{
this.StateSelectListViewModels = new StateSelectListViewModel(states);
this.CitySelectListViewModel = new CitySelectListViewModel();
}
public StateSelectListViewModel StateSelectListViewModels { set; get; }
public CitySelectListViewModel CitySelectListViewModel { set; get; }
}
Location [Controller]:
public class LocationController : Controller
{
private DALDbContext db = new DALDbContext();
// GET: Location/Create
public ActionResult Create()
{
LocationCreateViewModel locationCreateViewModel = new LocationCreateViewModel(db.States.ToList());
return View(locationCreateViewModel);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(LocationCreateViewModel pLocationCreateViewModel)
{
if (ModelState.IsValid)
{
//db.States.Add(state);
//db.SaveChanges();
return RedirectToAction("Index", "Home");
}
LocationCreateViewModel locationCreateViewModel = new LocationCreateViewModel(db.States.ToList());
return View(locationCreateViewModel);
}
public ActionResult CitySelectList(int? stateId)
{
CitySelectListViewModel citySelectListViewModel = new CitySelectListViewModel(db.Cities.Where(c => c.StateId == stateId).ToList());
return View(citySelectListViewModel);
}
}
Create [View]:
#model ViewModelExample.ViewModels.LocationCreateViewModel
....
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>State</h4>
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.StateSelectListViewModels.SelectedStateId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.StateSelectListViewModels.SelectedStateId, new SelectList(Model.StateSelectListViewModels.States, "Id", "Name"), "Select a State", htmlAttributes: new { #class = "form-control", #id = "StateSelectList" })
#Html.ValidationMessageFor(model => model.StateSelectListViewModels.SelectedStateId, "", new { #class = "text-danger" })
</div>
</div>
<div id="CityContainer">
#Html.Action("CitySelectList")
</div>
<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>
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
<script type="text/javascript">
$(function () {
// Fill City DropDownList
$('#StateSelectList').change(function () {
var selectedStateId = this.value;
$('#CityContainer').load('#Url.Action("CitySelectList")?stateId=' + selectedStateId);
});
});
</script>
}
CitySelectList [View]:
#model ViewModelExample.ViewModels.CitySelectListViewModel
....
<div class="form-group">
#Html.LabelFor(model => model.SelectedCityId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.SelectedCityId, new SelectList(Model.Cities, "Id", "Name"), "Select a City", htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.SelectedCityId, "", new { #class = "text-danger" })
</div>
</div>
I will show the execution of my example, and I will show the problem through the inspection of the ViewModel that I receive after the Post:
I select a State and a City, and I press Create.
I inspect the ViewModel received after the Post. We can see how CitySelectListViewModel is null, and what I want is to bring the last ViewModel that was updated through jQuery.
I admit that I have provided a long example, but it is the only way I found to explain what I need. Thanks in advance.
VS-Project of the example
I'ts because you are preventing the modelBinder to accurately bind to LocationCreateViewModel in your Create action when replacing the inner HTML of <div id="CityContainer"> (thats what you do with $('#CityContainer').load(...). You instruct the model binder to bind to
#model ViewModelExample.ViewModels.CitySelectListViewModel and as a result you get this HTML for the city select list:
One way of solving this is modifying CitySelectList.cshtml to:
#model ViewModelExample.ViewModels.LocationCreateViewModel
#{
Layout = null;
}
<div class="form-group">
#Html.LabelFor(model => model.CitySelectListViewModel.SelectedCityId,
htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model =>
model.CitySelectListViewModel.SelectedCityId, new
SelectList(Model.CitySelectListViewModel.Cities, "Id", "Name"), "Select a City", htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.CitySelectListViewModel.SelectedCityId, "", new { #class = "text-danger" })
</div>
</div>
and your CitySelectList action to:
public ActionResult CitySelectList(int? stateId)
{
LocationCreateViewModel locationCreateViewModel = new LocationCreateViewModel();
locationCreateViewModel.CitySelectListViewModel = new CitySelectListViewModel(db.Cities.Where(c => c.StateId == stateId).ToList());
return View(locationCreateViewModel);
}
But I would recommend custom model binding as well.

How to post objects in ViewModel?

I've got this viewmodel class with an integer Id and an object type.
public class MyViewModel
{
[Required]
public int MyId { get; set; }
public MyObject MyObject { get; set; }
}
MyObject Model is:
public class MyObject
{
[Key]
public int ObjId { get; set; }
public int Number { get; set; }
public string Name { get; set; }
}
This controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "MyId, MyObject.Number, MyObject.Name")]MyViewModel vm)
{
if (!ModelState.IsValid)
{
return View();
}
//do something!
}
The view is:
#model MyProject.Models.MyViewModel
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-group">
#Html.LabelFor(model => model.MyId , htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("MyId", null, "Select", htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.MyId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.MyObject.Number, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextBoxFor(model => model.MyObject.Number, new { #class = "form-control", type = "number", min = "1", max = "3", step = "1" })
#Html.ValidationMessageFor(model => model.MyObject.Number, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.MyObject.Name , htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextBoxFor(model => model.MyObject.Name , new { #class = "form-control", type = "number", min = "1", max = "3", step = "1" })
#Html.ValidationMessageFor(model => model.MyObject.Name , "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" formaction="#Url.Action("Create")" />
</div>
</div>
}
When I post data to controller, it recognize the MyId value but don't fill MyObject parameter. Any suggests to how to post an object with ViewModel?
Assuming that you have #model MyViewModel at the top of the view, there's no need to complicate it.
Make your submit on the view
<input type="submit" value="Create" class="btn btn-default" />
and change the signature of the post method
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(MyViewModel vm)
{
if (!ModelState.IsValid)
{
return View();
}
//do something!
}
The view model should then be bound.
Try below:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "MyId,MyObject, MyObject.Number, MyObject.Name")]MyViewModel vm)
{
if (!ModelState.IsValid)
{
return View();
}
}
You aren't including MyObject in Bind(include)

return list of ViewModel from model to controller [duplicate]

Whenever I submit the form the model passed into the controller is NULL. I've spent ages looking at this. I think I am missing something fundamental here.
#model VisitorPortal.Models.ReinviteVisitorModel
#using (Html.BeginForm("CreateMeeting", "Home", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
<h3>Reinvitation Details</h3>
<div>The information entered below will be sent to the visitors email address #Model.Info.Email</div>
<hr />
#Html.ValidationSummary("", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(m => m.NewMeeting.Title, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.NewMeeting.Title, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.NewMeeting.StartTime, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.NewMeeting.StartTime, new { #class = "datetimepicker form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.NewMeeting.EndTime, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.NewMeeting.EndTime, new { #class = "datetimepicker form-control" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
#Html.HiddenFor(m => m.NewMeeting.SubjectId, new { #Value = Model.Info.SubjectId })
<input type="submit" class="btn btn-default" value="Send Invite" />
</div>
</div>
}
The Model is:
public class Meeting
{
[Key]
public int Id { get; set; }
public string SubjectId { get; set; }
[Required]
[Display(Name = "Reason for invitation")]
public string Title { get; set; }
[Required]
[Display(Name = "Start Time")]
[DataType(DataType.Time)]
public DateTime StartTime { get; set; }
[Required]
[Display(Name = "End Time")]
[DataType(DataType.Time)]
public DateTime EndTime { get; set; }
public string HostEmail { get; set; }
public string HostMobile { get; set; }
}
public class MeetingsDBContext: DbContext
{
public DbSet<Meeting> Meetings { get; set; }
}
public class ReinviteVisitorModel
{
public Visitor Info;
public Meeting NewMeeting;
public List<Meeting> Meetings;
}
The Controller action is:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CreateMeeting(Meeting meeting)
{
return RedirectToAction("ReinviteVisitor2", "Home", new { visitorId = meeting.SubjectId });
}
I have fields in the model such as Id which I am expecting the database to populate which I was going to write in the the action CreateMeeting(). Do all fields in the Model have to be used in the form?
The model in your view is typeof ReinviteVisitorModel which means the signature of the POST method must match since your posting ReinviteVisitorModel
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CreateMeeting(ReinviteVisitorModel model)
alternatively you can use the Prefix property of BindAttribute to strip the NewMeeting prefix from the names of the form controls your are posting.
public ActionResult CreateMeeting([Bind(Prefix="NewMeeting")]Meeting model)
Side note: Remove new { #Value = Model.Info.SubjectId } from the hidden input and instead set the value of NewMeeting.SubjectId in the GET method before you pass the model to the view.
You need to pass ReinviteVisitorModel model in your Action
Update you action with this:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CreateMeeting(ReinviteVisitorModel model)
{
return RedirectToAction("ReinviteVisitor2", "Home", new { visitorId = model.NewMeeting.SubjectId });
}

There is no ViewData item of type 'IEnumerable<SelectListItem>' that has the key 'Practice' - MVC5

I am very new to MVC and have just added a cascading drop down to my create page so when a Practice is selected the Optician drop down is populated with the names of opticians that work at that practice.
Model:
public class Booking
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid BookingId { get; set; }
[ForeignKey("Patient")]
public Guid PatientId { get; set; }
public virtual Patient Patient { get; set; }
public IEnumerable<SelectListItem> PatientList { get; set; }
[ForeignKey("Practice")]
public Guid PracticeId { get; set; }
public virtual Practice Practice { get; set; }
public IEnumerable<SelectListItem> PracticeList { get; set; }
[ForeignKey("Optician")]
public Guid OpticianId { get; set; }
public virtual Optician Optician { get; set; }
public IEnumerable<SelectListItem> OpticiansList { get; set; }
[Display(Name = "Date")]
[DataType(DataType.Date)]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:dd/MM/yyyy}")]
public DateTime Date { get; set; }
[ForeignKey("Time")]
public Guid? TimeId { get; set; }
public virtual Time Time { get; set; }
public IEnumerable<SelectListItem> TimeList { get; set; }
public bool isAvail { get; set; }
}
My Controller:
// GET: Bookings1/Create
public ActionResult Create()
{
var practices = new SelectList(db.Practices, "PracticeId", "PracticeName");
ViewData["Practice"] = practices;
Booking booking = new Booking();
ConfigureCreateModel(booking);
return View(booking);
}
public void ConfigureCreateModel(Booking booking)
{
booking.PatientList = db.Patients.Select(p => new SelectListItem()
{
Value = p.PatientId.ToString(),
Text = p.User.FirstName
});
booking.TimeList = db.Times.Select(t => new SelectListItem()
{
Value = t.TimeId.ToString(),
Text = t.AppointmentTime
});
}
// POST: Bookings1/Create
// 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 Create(Booking booking)
{
// to ensure date is in the future
if (ModelState.IsValidField("Date") && DateTime.Now > booking.Date)
{
ModelState.AddModelError("Date", "Please enter a date in the future");
}
// Sets isAvail to false
booking.isAvail = false;
//Checks if model state is not valid
if (!ModelState.IsValid)
{
ConfigureCreateModel(booking);
return View(booking); // returns user to booking page
}
else // if model state is Valid
{
// Generates a new booking Id
booking.BookingId = Guid.NewGuid();
// Adds booking to database
db.Bookings.Add(booking);
// Saves changes to Database
db.SaveChanges();
// Redirects User to Booking Index
return RedirectToAction("Index");
}
}
My View:
<script src="~/Scripts/jquery-1.10.2.js"></script>
<script>
$(document).ready(function () {
$("#Optician").prop("disabled", true);
$("#Practice").change(function () {
$.ajax({
url : "#Url.Action("Opticians","Bookings")",
type : "POST",
data : {Id : $(this).val() }
}).done(function (opticianList) {
$("#Optician").empty();
for (var i = 0; i < opticianList.length; i++) {
$("#Optician").append("<option>" + opticianList[i] + "</option>");
}
$("#Optician").prop("disabled", false);
});
});
});
</script>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Booking</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.PatientId, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.PatientId, Model.PatientList, "-Please select-", new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.PatientId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.PracticeId, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("Practice", ViewData["Practice"] as SelectList,"-Please Select-", new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.PracticeId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.Label("Select Optician :", new { #class = "col-md-2 control-label" })
<div class="col-md-10">
<select id="Optician"></select>
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Date, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Date, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Date, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.TimeId, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.TimeId, Model.TimeList, "-Please select-", new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.TimeId, "", new { #class = "text-danger" })
</div>
</div>
<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>
The cascading drop down works as it should how ever when I click the button to create the appointment the following exception is thrown:
Exception:
An exception of type 'System.InvalidOperationException' occurred in System.Web.Mvc.dll but was not handled in user code
Additional information: There is no ViewData item of type 'IEnumerable' that has the key 'Practice'.
Any help would be greatly appreciated.
Thanks
You model already contains a property for the collection of practices
public IEnumerable<SelectListItem> PracticeList { get; set; }
although it should not contain
public virtual Practice Practice { get; set; }
In the GET method, you create a new SelectList for practices, but instead of assigning it to the model property, you add it to ViewData using
ViewData["Practice"] = practices;
and then in the view use
#Html.DropDownList("Practice", ViewData["Practice"] as SelectList, ..)
which is not even binding to a property in your model and would never post back to anything. Then when you return the view in the POST method (because your mode will always be invalid), you do not assign a value to ViewData["Practice"] so its null, hence the error.
Instead, in your ConfigureCreateModel() method, populate the PracticeList property (as your doing for PatientList) and remove the use of ViewData, and in the view use
#Html.DropDownListFor(model => model.PracticeId, Model.PracticeList, ...)
so your strongly binding to your model and when your submit the form, the value of PracticeId will be the value of the selected practice.
Side note: You will need to change your script to $("#PracticeId").change(function () { ...

MVC5 view drop down list from ViewBag

I'm new to MVC5/C# (fresh off a Silverlight project) and have a web application (not ASP.net) that I'm working on. I can't figure out how to get the value from a dropdown list that is populated from a ViewBag and not the model. Everything I've seen is geared towards ASP.NET and/or populating the dropdown from the model.
I have this model for shifts:
public class Shift
{
public Guid ShiftID { get; set; }
public string AreaOfOperation { get; set; }
public string UserName { get; set; }
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
}
And this for AreaOfOperations:
public class AreaOfOperations
{
public Guid AreaOfOperationsID { get; set; }
public String AreaOfOperation { get; set; }
public bool InUse { get; set; }
}
The relevant controller code, which populates the view nicely with a working dropdown:
public ActionResult Create(DateTime? datetime)
{
List<AreaOfOperations> list = db.AreaOfOperations.Where(i => i.InUse == true).OrderBy(aoo => aoo.AreaOfOperation).ToList();
ViewBag.DropDownAOOs = new SelectList(list, "AreaOfOperationsID", "AreaOfOperation");
Shift shift = new Shift();
shift.ShiftID = Guid.NewGuid();
shift.StartTime = DateTime.Now;
shift.UserName = User.Identity.Name;
return View(shift);
}
// POST: Shifts/Create
// 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 Create([Bind(Include = "ShiftID,AreaOfOperations,UserName,StartTime")] Shift shift)
{
try
{
if (ModelState.IsValid)
{
shift.ShiftID = Guid.NewGuid();
db.Shifts.Add(shift);
db.SaveChanges();
return RedirectToAction("Index");
}
}
catch (DataException /* dex */)
{
//Log the error (uncomment dex variable name and add a line here to write a log.
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.");
}
return View(shift);
}
And my view:
#model CRMgr5.Models.Shift
#{
ViewBag.Title = "Start Shift";
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Shift</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.AreaOfOperations, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("AreaOfOperation", ViewBag.DropDownAOOs as SelectList, new { htmlAttributes = new { #class = "form-control" } })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.UserName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.UserName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.UserName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.StartTime, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.StartTime, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.StartTime, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input id="btnStartShift" type="submit" value="Start Shift" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
Any help would be greatly appreciated. Thanks.
In the drop down list you named your select as "AreaOfOperation" but the model property is called "AreaOfOperations." Hence the binder will not be able to bind it.
As someone here already suggested you should use strongly typed html helpers such as DropDownListFor:
#Html.DropDownListFor(m => m.AreaOfOperations, ViewBag.DropDownAOOs as SelectList)
You did it for the label not sure why you opted not to use it when generating a drop down list?
I just recreated the whole thing and it worked fine
I removed the s of AreaOfOperations in your Bind Attribute
[Bind(Include = "ShiftID,AreaOfOperation(s),UserName,StartTime")]
As far as i know, you can remove this parameter attribute alltogether.
This is only used when you only want to bind to certain Attributes of your view model.
However there was one mistake: you have to repopulate the Select List if your ModelState is not valid. Otherwise your
return View(shift);
does not have the data to render a new SelectList.
Another approach is that you put the data in your ViewModel and initialize it in the default constructor. Then you dont have to worry about the data or casting.

Categories

Resources