Model binding doesn't work when multiple instances of the same partial view are called dynamically with ajax.actionlink in MVC 5 - ajax

I'm building a restaurant reservations system. When the user creates a reservation they can also decided what furniture will be used with the reservation. For this I created a partial view, so every time the user click on "add furniture" the partial view is loaded with ajax where the user can then specify what furniture and how many. They can add as many types of furniture as they want, meaning they can call the partial view as many times as they want. My problem comes in with the model binder, the model binder then have bind all those partial view instances to a list object of type "BistroReservations_ReservationsFurniture". Please help how to make the model binder work.
MainView (Please have a look at the bottom for the ajax.actionlink call "Add Furniture"
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>BistroReservations_Reservation</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.DateOfArrival, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.DateOfArrival, new { htmlAttributes = new { #class = "form-control datecontrol" } })
#Html.ValidationMessageFor(model => model.DateOfArrival, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.BistroReservations_ShiftID, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("BistroReservations_ShiftID", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.BistroReservations_ShiftID, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.BistroReservations_GuestID, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-6">
#Html.DropDownList("BistroReservations_GuestID", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.BistroReservations_GuestID, "", new { #class = "text-danger " })
#Ajax.ActionLink("Create a New Guest", "NewGuest", new AjaxOptions
{
HttpMethod = "GET",
UpdateTargetId = "NewGuest",
InsertionMode = InsertionMode.ReplaceWith,
})
</div>
</div>
<div id="NewGuest" class="form-group">
</div>
<br />
<div class="form-group">
#Html.LabelFor(model => model.ArrivalTime, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("ArrivalTime", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.ArrivalTime, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.LocationID, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("LocationID", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.LocationID, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.BistroReservations_TypeOfSeatingID, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("BistroReservations_TypeOfSeatingID", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.BistroReservations_TypeOfSeatingID, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.TableNoID, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("TableNoID", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.TableNoID, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.BistroReservations_StatusID, "BistroReservations_StatusID", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("BistroReservations_StatusID",null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.BistroReservations_StatusID, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Comment, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor( model => model.Comment, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Comment, "", new { #class = "text-danger" })
#Ajax.ActionLink("Add Furniture", "Furniture", new AjaxOptions
{
HttpMethod = "GET",
UpdateTargetId = "Furniture",
InsertionMode = InsertionMode.InsertAfter
})
</div>
</div>
<div id="Furniture" class="form-group">
</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>
}
ActionResult returning the furniture partial view
public PartialViewResult Furniture()
{
ViewBag.BistroReservations_FurnitureID = new SelectList(db.BistroReservations_Furnitures, "BistroReservations_FurnitureID", "Description");
return PartialView("_Furniture");
}
Furniture partial view
#model CdvPortal.Models.BistroReservations.BistroReservations_ReservationFurniture
<div class="form-horizontal">
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.BistroReservations_FurnitureID, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("BistroReservations_FurnitureID", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.BistroReservations_FurnitureID, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group ">
#Html.LabelFor(model => model.Quantity, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Quantity, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Quantity, "", new { #class = "text-danger" })
</div>
</div>
</div>
Furniture Model
public class BistroReservations_ReservationFurniture
{
public int BistroReservations_ReservationFurnitureID { get; set; }
public int BistroReservations_ReservationID { get; set; }
public virtual BistroReservations_Reservation BistroReservations_Reservation { get; set; }
[Display(Name="Furniture Type")]
public int BistroReservations_FurnitureID { get; set; }
public virtual BistroReservations_Furniture BistroReservations_Furniture { get; set; }
public int Quantity { get; set; }
}

You #Ajax.ActionLink() method is returning views with duplicate id attributes (invalid html) and duplicate name attributes which are not prefixed with the correct property name and do not include indexers for binding to a collection. For example if the collection property is named FurnitureItems then the html needed for the Quantity property of typeof BistroReservations_ReservationFurniture would need to be
<input type="text" name="FurnitureItems[0].Quantity" ...>
<input type="text" name="FurnitureItems[1].Quantity" ...>
You can use the BeginCollectionItem helper to generate the controls, which might look like (assuming the property is named FurnitureItems)
#model CdvPortal.Models.BistroReservations.BistroReservations_ReservationFurniture
#using (Html.BeginCollectionItem("FurnitureItems"))
{
....
#Html.LabelFor(m => m.Quantity, ..)
#Html.EditorFor(m => m.Quantity, ..)
#Html.ValidationMessageFor(m => m.Quantity, ..)
}
This will generate the correct prefix and add include an indexer based on a guid which also allows you to delete items from the collection. This article explains the usage in more detail
A pure client side alternative is shown in this answer. This gives better performance, but is harder to maintain since changing anything in the BistroReservations_ReservationFurniture model means updating the client side template.

Related

Single model and multiple views

I have a one model with Mymodel it contains the properties like name ,age,contact, phone,address,sortcode ,sysmcode like this and all these are required fields.
I have controller with name Home.
In HomeController i have action methods like
index
Contact(Mymodel model)
Code(Mymodel model)
I have navigate to the index page and provided the details and submitted ,it navigate to the Contact page.
while loading contact page it showing the validation error message.
Try this sample code for you reference. I tested and working fine.
Model Class :
public class Mymodel
{
[Required]
public string Name { get; set; }
[Required]
public string Age { get; set; }
[Required]
public string Contact { get; set; }
[Required]
public string Phone { get; set; }
[Required]
public string Address { get; set; }
[Required]
public string Sortcode { get; set; }
[Required]
public string Sysmcode { get; set; }
}
Home Controller Actions :
public class HomeController : Controller
{
public ActionResult Index()
{
//assigned value for test purposes.
Mymodel tt = new Mymodel()
{
Name = "Dumm",
Age = "55",
Address = "test",
Contact = "46516",
Phone = "516566",
Sortcode = "sdad",
Sysmcode = "asdad"
};
return View(tt);
}
public ActionResult Contact(Mymodel model)
{
ViewBag.Message = "Your contact page.";
return View(model);
}
}
And finally the Views :
Index.cshtml
#model WebApplication1.Models.Mymodel
#Scripts.Render("~/bundles/jquery")
#Scripts.Render("~/bundles/jqueryval")
#using (Html.BeginForm("Contact", "Home", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Mymodel</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.Age, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Age, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Age, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Contact, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Contact, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Contact, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Phone, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Phone, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Phone, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Address, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Address, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Address, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Sortcode, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Sortcode, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Sortcode, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Sysmcode, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Sysmcode, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Sysmcode, "", 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>
Contact.cshtml
#model WebApplication1.Models.Mymodel
<div>
<h4>Mymodel</h4>
<hr />
<dl class="dl-horizontal">
<dt>
#Html.DisplayNameFor(model => model.Name)
</dt>
<dd>
#Html.DisplayFor(model => model.Name)
</dd>
<dt>
#Html.DisplayNameFor(model => model.Age)
</dt>
<dd>
#Html.DisplayFor(model => model.Age)
</dd>
<dt>
#Html.DisplayNameFor(model => model.Contact)
</dt>
<dd>
#Html.DisplayFor(model => model.Contact)
</dd>
<dt>
#Html.DisplayNameFor(model => model.Phone)
</dt>
<dd>
#Html.DisplayFor(model => model.Phone)
</dd>
<dt>
#Html.DisplayNameFor(model => model.Address)
</dt>
<dd>
#Html.DisplayFor(model => model.Address)
</dd>
<dt>
#Html.DisplayNameFor(model => model.Sortcode)
</dt>
<dd>
#Html.DisplayFor(model => model.Sortcode)
</dd>
<dt>
#Html.DisplayNameFor(model => model.Sysmcode)
</dt>
<dd>
#Html.DisplayFor(model => model.Sysmcode)
</dd>
</dl>
</div>
<p>
#Html.ActionLink("Edit", "Edit", new { /* id = Model.PrimaryKey */ }) |
#Html.ActionLink("Back to List", "Index")
</p>
Validations working fine.
Hope this helps.

MVC validation focusing the date time control on validation fail

I am having my view as follows
#using (Html.BeginForm("Apply", "Leave", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="row">
<div class="col-xs-12 col-sm-6 col-md-6">
<div class="form-group">
Leave Type
#Html.DropDownListFor(m => m.LeaveTypeId, Model.LeaveType, "Choose...", new { #class = "form-control", #tabindex = "1" })
#Html.ValidationMessageFor(m => m.LeaveTypeId, "", new { #class = "label-danger" })
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-sm-6 col-md-6">
<div class="form-group">
From Date
#Html.TextBoxFor(m => m.FromDate, new { #class = "form-control", #tabindex = "2" })
#Html.ValidationMessageFor(m => m.FromDate, "", new { #class = "label-danger" })
</div>
</div>
}
When I click on apply button it is auto focusing the next control as my tab index was set. So is there any way to focus but not display the datetime control

I'm trying to send two parameters from view to controller but the second is always null

I'm trying to send two parameters (HttpPostedFileBase and the model) from the view to the Create method in the controller but the variable HttpPostedFile photos always gets null values.
Here is the Controller Code:
public async Task <ActionResult> Create([Bind(Include =
"Id,Name,Description")] Models.Environment environment, HttpPostedFileBase photos)
{
if (ModelState.IsValid)
{
if (photos!=null)
{
DataBlobImage dataBlobImage = new DataBlobImage();
environment.Logo = await
dataBlobImage.CreateImage("environment",
environment.Id.ToString(), photos);
}
//Creation date
environment.CreationDate = DateTime.Now;
//Get the creation user ID
environment.CreationUser = 1;
//By default when you create a user is active
environment.Active = true;
db.Environment.Add(environment);
await db.SaveChangesAsync();
return Json(new { success = true });
}
return View(environment);
}
View code:
#using (Html.BeginForm("ModalCreate", "Environment",
FormMethod.Post,
new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
<div class="modal-header create-window">
<button type="button" class="close"
data-dismiss="modal" aria- hidden="true">×</button>
<h4 class="modal-title" id="myModalLabel">Crear nuevo Ambiente</h4>
</div>
<div class="modal-body">
<div class="form-horizontal">
#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.Description,
htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Description,
new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Description, "",
new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-10">
<label for="file">Subir Imagen:</label>
<input id="photos" name="photos" type="file"
style="width: 100%;" />
</div>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal">Cancelar</button>
<input class="btn btn-primary" type="submit" value="Crear" />
</div>
</div>
</div>
}
I think you don't need to pass an additional parameter for that. You can alway access it like below inside your action method:
HttpPostedFileBase file = Request.Files["NameOfYourFileUploadControl"];
Hope this will help.

Show only server side errors in validation summary

I have the following view:
#model MyProject.Models.RegisterViewModel
<div class="content">
<div class="wrapper">
<h2>E-Bill Saver Registration</h2>
<hr />
<div class="columns top-gap">
<div class="first cell width-6 dark-blue-headings">
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#*#Html.ValidationSummary("", new { #class = "text-danger" })*#
<div class="form-horizontal">
<div class="form-group">
#Html.Label("Account Number", htmlAttributes: new { #class = "control-label col-md-2 bold" })
<br />#Html.ValidationMessageFor(model => model.AccountNo, "", new { #class = "text-danger" })
<div class="col-md-10">
#Html.EditorFor(model => model.AccountNo, new { htmlAttributes = new { #class = "form-control input" } })
</div>
</div>
<div class="form-group">
#Html.Label("MPNumber", htmlAttributes: new { #class = "control-label col-md-2 bold" })
<br />#Html.ValidationMessageFor(model => model.MPNumber, "", new { #class = "text-danger" })
<div class="col-md-10">
#Html.EditorFor(model => model.MPRN, new { htmlAttributes = new { #class = "form-control input" } })
</div>
</div>
<div class="form-group">
#Html.Label("Email Address", htmlAttributes: new { #class = "control-label col-md-2 bold" })
<br />#Html.ValidationMessageFor(model => model.Email, "", new { #class = "text-danger" })
<div class="col-md-10">
#Html.EditorFor(model => model.Email, new { htmlAttributes = new { #class = "form-control input" } })
</div>
</div>
<div class="form-group">
#Html.Label("Confirm Email Address", htmlAttributes: new { #class = "control-label col-md-2 bold" })
<br />#Html.ValidationMessageFor(model => model.ConfirmEmail, "", new { #class = "text-danger" })
<div class="col-md-10">
#Html.EditorFor(model => model.ConfirmEmail, new { htmlAttributes = new { #class = "form-control input" } })
</div>
</div>
<div class="form-group">
#Html.Label("Password", htmlAttributes: new { #class = "control-label col-md-2 bold" })
<br />#Html.ValidationMessageFor(model => model.Password, "", new { #class = "text-danger" })
<div class="col-md-10">
#Html.EditorFor(model => model.Password, new { htmlAttributes = new { #class = "form-control input" } })
</div>
</div>
<div class="form-group">
#Html.Label("Confirm Password", htmlAttributes: new { #class = "control-label col-md-2 bold" })
<br />#Html.ValidationMessageFor(model => model.ConfirmPassword, "", new { #class = "text-danger" })
<div class="col-md-10">
#Html.EditorFor(model => model.ConfirmPassword, new { htmlAttributes = new { #class = "form-control input" } })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Register" class="opc_button" />
</div>
</div>
</div>
}
</div>
</div>
</div>
If there are validation errors on any of the fields then the error message is displayed below the label as expected and wanted. On my controller however I have some custom logic errors and I am adding them to the modelstate as follows:
else
{
//This email address is already assigned to a user
ModelState.AddModelError("Email Address : ", "Error! You must enter an email address that is unique to your account.");
return View(model);
}
If I uncomment the validationsummary on my page then it will display the custom server side error but it will also display the single validation errors which I want underneath the labels. Is there any way that I can set the validation summary to ignore the values that are being listed individually and only return server side validation errors?
The first string parameter of ModelState.AddModelError method is the key later used to bind the message to the model property.
If you want to display that error message next to email label, make first parameter of AddModelError match your property name:
ModelState.AddModelError("Email", "Error! Email already used...");

Value not populated in the textbox

I have a textbox with id Name.
View:
<div class="form-group">
<div class="col-md-6 control-label">
#Html.LabelFor(m => m.Name, htmlAttributes: new { #class = "" })
<i class="required">*</i>
</div>
<div class="col-md-6">
#Html.EditorFor(model => model.Name, new { htmlAttributes = new { #class = "form-control", autocomplete = "off" } })
#Html.ValidationMessageFor(model => model.Name, "", new { #class = "text-danger" })
</div>
</div>
and in the developer tool:
However the Textbox doesn't contain any value. and it is blank.
Can anyone find what I am missing? I have also tried using ModelState.Clear().

Resources