Why does validation break on partial view? - c#

This validation should work, but the post fires right past it if there are no selected values in the dropdown. When moved to another non-partial the validation works just fine. Edited for brevity.
ViewModel:
public class BuilderVM
{
[Display(Name = "Select A Task")]
[Required]
public int? TaskId { get; set; }
public GenericSelectList Tasks { get; set; }
}
Parent View (the partial view is rendered at the bottom of this page after a post. as you may have noticed, the ajax options dictate that the partial view will be rendered in div id="MoveOn"):
#{
ViewBag.Title = "Builder";
AjaxOptions ajaxOpts = new AjaxOptions
{
LoadingElementDuration = 2,
LoadingElementId = "removeChoice",
UpdateTargetId = "MoveOn"
};
}
<div id="removeChoice">
#using (Ajax.BeginForm("Selected", ajaxOpts))
{
<fieldset>
<div>
//Data For Submission (This data validates perfectly before post)
</div>
<p><input type="submit" value="Go" /></p>
</fieldset>
}
</div>
<div id="MoveOn"></div>
Partial View (rendered after a post from parent view):
#model namespace.BuilderVM
#{
AjaxOptions ajaxOpts = new AjaxOptions
{
UpdateTargetId = "Entry",
LoadingElementDuration = 2,
LoadingElementId = "RemoveEntry"
};
}
<div id="RemoveEntry">
<h2>Details</h2>
#using (Ajax.BeginForm("Data", ajaxOpts))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Data</legend>
<div>
<span class="label">#Html.LabelFor(model => model.TaskId)</span>
<span class="content">
#Html.DropDownListFor(
model => model.TaskId,
new SelectList(
Model.Tasks.Values,
"Id",
"DisplayFields",
Model.Tasks.StartValue
),
Model.Tasks.Message
)
</span>#Html.ValidationMessageFor(model => model.TaskId)
</div>
<p><input type="submit" value="Add Work Completed Data" /></p>
</fieldset>
}
</div>
<div id="Entry"></div>
Although the dropdownlistfor is bound to model.TaskId which is annotated with [Required] when the post button is clicked (input type="submit") and there is no value selected in the dropdownlist the post goes through instead of stopping and appending the validation message. I am not sure what to do to fix this, as it works just fine when copy pasted to a regular view. Why wont the partial view validation work?

The problem is that the validator just loads in the beginning (with $(document).ready()).
What you can do is the following (insert in the partial view):
<script>
$(function() {
$.validator.unobtrusive.parse('.Content');
});
</script>
From the given information I assume this is your problem. Hope this helps you.

Related

HTM BeginCollectionItem returns only first item of collection

I have the following problem:
A collection of "Gifts" gets passed as a parameter to a view.
Each item inside this collection is a object from the Model class "Gift".
There are only two properties inside this class: name and price.
I put the class inside the controller for the sake of simplicity.
This is the action method:
// GET: Order/CreateGift
[HttpGet]
public ActionResult CreateGift()
{
var initialData = new[]
{
new Gift{ Name = "Tricycle", Price = 69.95 },
new Gift{ Name = "PS4 game", Price = 29.99 },
new Gift{ Name = "Lazergun", Price = 49.99}
};
return View(initialData);
}
On the view I can dynamically add new items to the collection, thanks to the BeginCollectionItem Html helper.
This is my View:
#model IEnumerable<DemoWebShop.Controllers.Gift>
<script src="~/Scripts/jquery-3.4.1.min.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script type="text/javascript" src="http://ajax.cdnjs.com/ajax/libs/json2/20110223/json2.js"></script>
<script type="text/javascript">
$(document).ready(function () {
$("#addItem").click(function () {
$.ajax({
url: this.href,
cache: false,
success: function (html) { $("#editorRows").append(html); }
});
return false;
});
}); // end document.ready function
</script>
<h2>Gift List</h2>
What do you want for your birthday?
#using (Html.BeginForm())
{
<div class="form-horizontal">
<div id="editorRows">
#foreach (var item in Model)
{
Html.RenderPartial("GiftEditorRow", item);
}
</div>
#Html.ActionLink("Add another...", "BlankEditorRow", null, new { id = "addItem" })
<input type="submit" value="Finished" />
#*<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Aanmaken" class="btn btn-default" />
</div>
</div>*#
</div>
}
This is the partial view called "GiftEditorRow" :
#using HtmlHelpers.BeginCollectionItem
#model DemoWebShop.Controllers.Gift
#{
Layout = null;
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="editorRow">
#using (Html.BeginCollectionItem("gifts"))
{
<label>Name: </label>
#Html.TextBoxFor(x => x.Name);
<label>Price: </label>
#Html.TextBoxFor(x => x.Price, new { size = 4 });
<br />
}
</div>
}
Inside my controller I also have this actionmethod:
public ViewResult BlankEditorRow()
{
return View("GiftEditorRow", new Gift());
}
Only the First item that already existed inside the collection, gets passed to the HTTP Post method in my controller.
I found my mistake.
The partial view "GiftEditorRow" still contains the following:
#using (Html.BeginForm()){.....}
If you remove this, it should work.
This is my partial view now:
#using (Html.BeginCollectionItem("gifts"))
{
<label>Name: </label>
#Html.TextBoxFor(Model => Model.Name);
<label>Price: </label>
#Html.TextBoxFor(Model => Model.Price, new { size = 4 });
<br />
}
Now I get all items instead of just the first one.

MVC 5 Viewmodel binding works but post back is partial filled

I have a parameterless Index for the HttpGet which works. But when I post it the HttpPost version of Index is invoked and the viewmodel object is passed in, but there is only the value of the dropdown in it. The rest is null (products, title)
[HttpPost]
public ActionResult Index(ProductsViewModel pvm)
{
// breakpoint on line 36, shows that pvm.Title is null and Products too.
return View(pvm);
}
My compilable and running example can be downloaded from my OneDrive http://1drv.ms/1zSsMkr
My view:
#model KleinKloteProductOverzicht.Models.ProductsViewModel
#using (Html.BeginForm("Index", "Products"))
{
<h2>#Html.DisplayFor(m => m.Title)</h2>
<input type="submit" value="post dit" /><br/>
<div class="row">
<div class="col-lg-2 col-md-2">
#Html.DropDownListFor(x => x.CurrentSort, EnumHelper.GetSelectList(typeof(SortOptions)), new { #class = "multiselect"})
</div>
</div>
if (Model.Products.Count() > 0)
{
<div class="row">
#foreach (var item in Model.Products)
{
#Html.DisplayFor(i => item.Name);
}
</div>
}
}
If I have this view model:
public class ViewModel
{
public string Name {get;set;}
public string SelectedLocation {get;set;}
public IEnumerable<SelectListItem> Locations {get;set;}
}
And your actions look like this:
public ActionResult MyForm()
{
var vm = new ViewModel
{
Locations = context.Locations.ToList() // Some database call
}
return View(vm);
}
[HttpPost]
public ActionResult MyForm(ViewModel vm)
{
vm.Locations // this is null
}
It is null because the model binder can't find a form control that is setting its data.
The <form> must set some data in the view for the model binder to pick it up.
<form>
Name: <input type="text" id="name" />
</form>
This will set the Name property on the view model, because the model bind can see the id of the form control and uses that to know what to bind to.
So in terms of your view, you need to make sure you wrap any content that you want to post back to the server with #using(Html.BeginForm())
Anyway this is my guess.
Well, you seem to be confused as to how [HttpPost] and form tags interact with eachother.
You see, when .NET MVC binds your parameters in your controller actions, it tries to derive that data from the request. For [HttpGet] it does this by looking at the query string.
For [HttpPost] calls, it also looks at the Request.Form. This variable is populated with the values of all input fields that were inside the form you submitted.
Now, this is your view:
#using (Html.BeginForm("Index", "Products"))
{
<h2>#Html.DisplayFor(m => m.Title)</h2>
<input type="submit" value="post dit" /><br/>
<div class="row">
<div class="col-lg-2 col-md-2">
#Html.DropDownListFor(x => x.CurrentSort, EnumHelper.GetSelectList(typeof(SortOptions)), new { #class = "multiselect" })
</div>
</div>
if (Model.Products.Count() > 0)
{
<div class="row">
#foreach (var item in Model.Products)
{
#Html.DisplayFor(i => item.Name);
}
</div>
}
}
You only have one select tag (generated by Dropdownlistfor) but no other inputs. That's why .NET MVC cannot infer any other data for your view model.
If you change your view to this:
#model KleinKloteProductOverzicht.Models.ProductsViewModel
#using (Html.BeginForm("Index", "Products"))
{
<h2>#Html.DisplayFor(m => m.Title)</h2>
<input type="submit" value="post dit" /><br/>
<div class="row">
<div class="col-lg-2 col-md-2">
#Html.DropDownListFor(x => x.CurrentSort, EnumHelper.GetSelectList(typeof(SortOptions)), new { #class = "multiselect" })
</div>
</div>
if (Model.Products.Count() > 0)
{
<div class="row">
#for (var i = 0; i < Model.Products.Count; i++)
{
#Html.DisplayFor(model => model.Products[i].Name)
#Html.HiddenFor(model => model.Products[i].ID)
}
</div>
}
}
You'll see I've added a hidden input (<input type="hidden">) for the product id. Note that the product name still will be null.
I would suggest you follow a tutorial on .NET MVC and read up on some of the concepts behind it, because the very fact that you ask this question reveals that you have much to learn.
Best of luck!
P.S. One last tip: #Html.Blablabla writes directly to your view. You usually don't need that ";" at the end, because it will be inside your generated html.
Your property is not associated with a "postable" control, therefore it will not be submitted along with the form data. If your really want to get the value in your Title property, just set it as a hidden input.
#Html.HiddenFor(m => m.Title)
A label will not be posted when submitting a form but an input will. This is exactly what HiddenFor does; it creates a hidden input element which will be picked up by the form submit.

Rendering partial view inside a view using AJAX

I'm trying to refresh a partial view inside of a view when a form is submitted. However, whenever I try it just renders the partial view as a normal view. Can someone tell me what I'm doing wrong?
Controller:
public ActionResult ChangeHeatName(string heatName, string updatedHeat)
{
string user = User.Identity.Name;
HomeModel H = new HomeModel();
H.ChangeHeatName(heatName, updatedHeat, user);
ChemViewModel mySlagViewModel = new ChemViewModel();
mySlagViewModel = H.QueryResults(heatName);
return PartialView("PartialChemAnalysis", mySlagViewModel);
}
Partial view form (contained in partial view, not main view):
#using (Ajax.BeginForm("ChangeHeatName", "Home", new AjaxOptions(){UpdateTargetId = "chemDiv" InsertionMode = InsertionMode.Replace}))
{
<section>
Heat Name:<input type="text" name="heatName" value="#Html.ValueFor(x => x.heatname)" style ="width:100px"/>
Change to:<input type="text" name="updatedHeat" value="" style="width: 100px" />
<input type="submit" name="ChangeHeatName" value="Change" />
</section>
}
Index view where partial view is being rendered:
#if(ViewBag.SearchKey == null)
{
<div class="content-wrapper">
<hgroup class="title">
<h1>#HttpContext.Current.User.Identity.Name</h1>
<h2>#ViewBag.Message</h2>
</hgroup>
</div>
}
#using (Html.BeginForm("Index", "Home", "POST"))
{
<div class="searchField">
<input type="text" class="search-query" name="heatSearch" placeholder="Search">
<button class="btn btn-success" type="submit">Search</button>
<br />
#if (ViewBag.AverageSuccessful == true)
{
<input type="text" name="AvgConfirmation" class="search-query" value="Average Submitted Successfully" width:"400px" placeholder="Search" />
}
</div>
}
#if(ViewBag.SearchKey != null)
{
<div>
<div id ="chemDiv">
#Html.Action("PartialChemAnalysis", "Home", (string)ViewBag.SearchKey)
</div>
<div id ="slafDiv">
#Html.Action("PartialSlagView", "Home", (string)ViewBag.SearchKey)
</div>
</div>
}
Index controller that passes SearchKey:
[HttpPost]
public ActionResult Index(string heatSearch)
{
ViewBag.SearchKey = heatSearch;
return View();
}
Currently your ajax.beginform is in your partial view, that's all fine and dandy, but your partialview is not rendered inside your index, so really your never doing the ajax replace logic you're just calling a the action method and getting a full page refresh of the partial view.
here's what would work.
#if(ViewBag.SearchKey != null)
{
<div>
<div id ="chemDiv">
#Html.Partial("ChangeHeatName")
</div>
<div id ="slafDiv">
#Html.Action("PartialSlagView", "Home", (string)ViewBag.SearchKey)
</div>
</div>
}
Now your Ajax.Beginform is rendered in the index view, and when the button is clicked it will refresh.
Edit: you'll need to do something with #Html.Action("PartialChemAnalysis", "Home", (string)ViewBag.SearchKey) possibly stick it in your partial view, because everything in "chemDiv" will now be replaced upon update.
You're not specifying POST in Ajax.BeginForm(). Try this:
#using (Ajax.BeginForm("ChangeHeatName", "Home", FormMethod.Post,
new AjaxOptions(){UpdateTargetId = "chemDiv" InsertionMode = InsertionMode.Replace}))
{...}
Also, stick a breakpoint on your controller action and step through it, see if it's actually hitting return PartialView() or skipping over it.
Posting this because it's not an intuitive fix. Apparently there are issues with MVC 4 and jQuery 1.9.1 so to get this to work I had to change my reference to jQuery 1.7.1

MVC 4 Edit modal form using Bootstrap

I'm using MVC 4 and Entity Framework to develop an intranet web application. I have a list of persons which can be modify by an edit action. I wanted to make my app more dynamic by using modal forms. So I tried to put my edit view into my Bootstrap modal and I have 2 questions about it :
Should I use a simple or a partial view?
How can I perform the validation (actually it work but it redirects me to my original view so not in the modal form)
I think I have to use AJAX and/or jQuery but I'm new to these technologies. Any help would be appreciated.
EDIT : My Index View :
#model IEnumerable<BuSIMaterial.Models.Person>
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<br />
<div class="group">
<input type="button" value="New person" class="btn" onclick="location.href='#Url.Action("Create")';return false;"/>
<input type="button" value="Download report" class="btn" onclick="location.href='#Url.Action("PersonReport")';return false;"/>
</div>
#using (Html.BeginForm("SelectedPersonDetails", "Person"))
{
<form class="form-search">
<input type="text" id="tbPerson" name="tbPerson" placeholder="Find an employee..." class="input-medium search-query">
<button type="submit" class="btn">Search</button>
</form>
}
<table class="table">
<thead>
<tr>
<th>Firstname</th>
<th>Lastname</th>
<th>Start Date</th>
<th>End Date</th>
<th>Details</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
#foreach (BuSIMaterial.Models.Person item in ViewBag.PageOfPersons)
{
<tr>
<td>#item.FirstName</td>
<td>#item.LastName</td>
<td>#item.StartDate.ToShortDateString()</td>
<td>
#if (item.EndDate.HasValue)
{
#item.EndDate.Value.ToShortDateString()
}
</td>
<td>
<a class="details_link" data-target-id="#item.Id_Person">Details</a>
</td>
<td>
<div>
<button class="btn btn-primary edit-person" data-id="#item.Id_Person">Edit</button>
</div>
</td>
</tr>
<tr>
<td colspan="6">
<table>
<tr>
<th>National Number</th>
<td>#item.NumNat</td>
</tr>
<tr>
<th>Vehicle Category</th>
<td>#item.ProductPackageCategory.Name</td>
</tr>
<tr>
<th>Upgrade</th><td>#item.Upgrade</td>
</tr>
<tr>
<th>House to work</th>
<td>#item.HouseToWorkKilometers.ToString("G29")</td>
</tr>
</table>
<div id="details_#item.Id_Person"></div>
</td>
</tr>
}
</tbody>
</table>
<div class="modal hide fade in" id="edit-member">
<div id="edit-person-container"></div>
</div>
#section Scripts
{
#Scripts.Render("~/bundles/jqueryui")
#Styles.Render("~/Content/themes/base/css")
<script type="text/javascript" language="javascript">
$(document).ready(function () {
$('#tbPerson').autocomplete({
source: '#Url.Action("AutoComplete")'
});
$(".details_link").click(function () {
var id = $(this).data("target-id");
var url = '/ProductAllocation/ListByOwner/' + id;
$("#details_"+ id).load(url);
});
$('.edit-person').click(function () {
var url = "/Person/EditPerson";
var id = $(this).attr('data-id');
$.get(url + '/' + id, function (data) {
$('#edit-person-container').html(data);
$('.edit-person').modal('show');
});
});
});
</script>
}
My Partial View :
#model BuSIMaterial.Models.Person
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 id="myModalLabel">Edit</h3>
</div>
<div>
#using (Ajax.BeginForm("EditPerson", "Person", FormMethod.Post,
new AjaxOptions
{
InsertionMode = InsertionMode.Replace,
HttpMethod = "POST",
UpdateTargetId = "list-of-people"
}))
{
#Html.ValidationSummary()
#Html.AntiForgeryToken()
<div class="modal-body">
<div class="editor-field">
#Html.TextBoxFor(model => model.FirstName, new { maxlength = 50 })
#Html.ValidationMessageFor(model => model.FirstName)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.LastName, new { maxlength = 50 })
#Html.ValidationMessageFor(model => model.LastName)
</div>
</div>
<div class="modal-footer">
<button class="btn btn-inverse" type="submit">Save</button>
</div>
}
You should use partial views. I use the following approach:
Use a view model so you're not passing your domain models to your views:
public class EditPersonViewModel
{
public int Id { get; set; } // this is only used to retrieve record from Db
public string Name { get; set; }
public string Age { get; set; }
}
In your PersonController:
[HttpGet] // this action result returns the partial containing the modal
public ActionResult EditPerson(int id)
{
var viewModel = new EditPersonViewModel();
viewModel.Id = id;
return PartialView("_EditPersonPartial", viewModel);
}
[HttpPost] // this action takes the viewModel from the modal
public ActionResult EditPerson(EditPersonViewModel viewModel)
{
if (ModelState.IsValid)
{
var toUpdate = personRepo.Find(viewModel.Id);
toUpdate.Name = viewModel.Name;
toUpdate.Age = viewModel.Age;
personRepo.InsertOrUpdate(toUpdate);
personRepo.Save();
return View("Index");
}
}
Next create a partial view called _EditPersonPartial. This contains the modal header, body and footer. It also contains the Ajax form. It's strongly typed and takes in our view model.
#model Namespace.ViewModels.EditPersonViewModel
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 id="myModalLabel">Edit group member</h3>
</div>
<div>
#using (Ajax.BeginForm("EditPerson", "Person", FormMethod.Post,
new AjaxOptions
{
InsertionMode = InsertionMode.Replace,
HttpMethod = "POST",
UpdateTargetId = "list-of-people"
}))
{
#Html.ValidationSummary()
#Html.AntiForgeryToken()
<div class="modal-body">
#Html.Bootstrap().ControlGroup().TextBoxFor(x => x.Name)
#Html.Bootstrap().ControlGroup().TextBoxFor(x => x.Age)
</div>
<div class="modal-footer">
<button class="btn btn-inverse" type="submit">Save</button>
</div>
}
Now somewhere in your application, say another partial _peoplePartial.cshtml etc:
<div>
#foreach(var person in Model.People)
{
<button class="btn btn-primary edit-person" data-id="#person.PersonId">Edit</button>
}
</div>
// this is the modal definition
<div class="modal hide fade in" id="edit-person">
<div id="edit-person-container"></div>
</div>
<script type="text/javascript">
$(document).ready(function () {
$('.edit-person').click(function () {
var url = "/Person/EditPerson"; // the url to the controller
var id = $(this).attr('data-id'); // the id that's given to each button in the list
$.get(url + '/' + id, function (data) {
$('#edit-person-container').html(data);
$('#edit-person').modal('show');
});
});
});
</script>
I prefer to avoid using Ajax.BeginForm helper and do an Ajax call with JQuery. In my experience it is easier to maintain code written like this. So below are the details:
Models
public class ManagePeopleModel
{
public List<PersonModel> People { get; set; }
... any other properties
}
public class PersonModel
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
... any other properties
}
Parent View
This view contains the following things:
records of people to iterate through
an empty div that will be populated with a modal when a Person needs to be edited
some JavaScript handling all ajax calls
#model ManagePeopleModel
<h1>Manage People</h1>
#using(var table = Html.Bootstrap().Begin(new Table()))
{
foreach(var person in Model.People)
{
<tr>
<td>#person.Id</td>
<td>#Person.Name</td>
<td>#person.Age</td>
<td>#html.Bootstrap().Button().Text("Edit Person").Data(new { #id = person.Id }).Class("btn-trigger-modal")</td>
</tr>
}
}
#using (var m = Html.Bootstrap().Begin(new Modal().Id("modal-person")))
{
}
#section Scripts
{
<script type="text/javascript">
// Handle "Edit Person" button click.
// This will make an ajax call, get information for person,
// put it all in the modal and display it
$(document).on('click', '.btn-trigger-modal', function(){
var personId = $(this).data('id');
$.ajax({
url: '/[WhateverControllerName]/GetPersonInfo',
type: 'GET',
data: { id: personId },
success: function(data){
var m = $('#modal-person');
m.find('.modal-content').html(data);
m.modal('show');
}
});
});
// Handle submitting of new information for Person.
// This will attempt to save new info
// If save was successful, it will close the Modal and reload page to see updated info
// Otherwise it will only reload contents of the Modal
$(document).on('click', '#btn-person-submit', function() {
var self = $(this);
$.ajax({
url: '/[WhateverControllerName]/UpdatePersonInfo',
type: 'POST',
data: self.closest('form').serialize(),
success: function(data) {
if(data.success == true) {
$('#modal-person').modal('hide');
location.reload(false)
} else {
$('#modal-person').html(data);
}
}
});
});
</script>
}
Partial View
This view contains a modal that will be populated with information about person.
#model PersonModel
#{
// get modal helper
var modal = Html.Bootstrap().Misc().GetBuilderFor(new Modal());
}
#modal.Header("Edit Person")
#using (var f = Html.Bootstrap.Begin(new Form()))
{
using (modal.BeginBody())
{
#Html.HiddenFor(x => x.Id)
#f.ControlGroup().TextBoxFor(x => x.Name)
#f.ControlGroup().TextBoxFor(x => x.Age)
}
using (modal.BeginFooter())
{
// if needed, add here #Html.Bootstrap().ValidationSummary()
#:#Html.Bootstrap().Button().Text("Save").Id("btn-person-submit")
#Html.Bootstrap().Button().Text("Close").Data(new { dismiss = "modal" })
}
}
Controller Actions
public ActionResult GetPersonInfo(int id)
{
var model = db.GetPerson(id); // get your person however you need
return PartialView("[Partial View Name]", model)
}
public ActionResult UpdatePersonInfo(PersonModel model)
{
if(ModelState.IsValid)
{
db.UpdatePerson(model); // update person however you need
return Json(new { success = true });
}
// else
return PartialView("[Partial View Name]", model);
}
In reply to Dimitrys answer but using Ajax.BeginForm the following works at least with MVC >5 (4 not tested).
write a model as shown in the other answers,
In the "parent view" you will probably use a table to show the data.
Model should be an ienumerable. I assume, the model has an id-property. Howeverm below the template, a placeholder for the modal and corresponding javascript
<table>
#foreach (var item in Model)
{
<tr> <td id="editor-success-#item.Id">
#Html.Partial("dataRowView", item)
</td> </tr>
}
</table>
<div class="modal fade" id="editor-container" tabindex="-1"
role="dialog" aria-labelledby="editor-title">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="editor-content-container"></div>
</div>
</div>
<script type="text/javascript">
$(function () {
$('.editor-container').click(function () {
var url = "/area/controller/MyEditAction";
var id = $(this).attr('data-id');
$.get(url + '/' + id, function (data) {
$('#editor-content-container').html(data);
$('#editor-container').modal('show');
});
});
});
function success(data,status,xhr) {
$('#editor-container').modal('hide');
$('#editor-content-container').html("");
}
function failure(xhr,status,error) {
$('#editor-content-container').html(xhr.responseText);
$('#editor-container').modal('show');
}
</script>
note the "editor-success-id" in data table rows.
The dataRowView is a partial containing the presentation of an model's item.
#model ModelView
#{
var item = Model;
}
<div class="row">
// some data
<button type="button" class="btn btn-danger editor-container" data-id="#item.Id">Edit</button>
</div>
Write the partial view that is called by clicking on row's button (via JS $('.editor-container').click(function () ... ).
#model Model
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
<h4 class="modal-title" id="editor-title">Title</h4>
</div>
#using (Ajax.BeginForm("MyEditAction", "Controller", FormMethod.Post,
new AjaxOptions
{
InsertionMode = InsertionMode.Replace,
HttpMethod = "POST",
UpdateTargetId = "editor-success-" + #Model.Id,
OnSuccess = "success",
OnFailure = "failure",
}))
{
#Html.ValidationSummary()
#Html.AntiForgeryToken()
#Html.HiddenFor(model => model.Id)
<div class="modal-body">
<div class="form-horizontal">
// Models input fields
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Save</button>
</div>
}
This is where magic happens: in AjaxOptions, UpdateTargetId will replace the data row after editing, onfailure and onsuccess will control the modal.
This is, the modal will only close when editing was successful and there have been no errors, otherwise the modal will be displayed after the ajax-posting to display error messages, e.g. the validation summary.
But how to get ajaxform to know if there is an error? This is the controller part, just change response.statuscode as below in step 5:
the corresponding controller action method for the partial edit modal
[HttpGet]
public async Task<ActionResult> EditPartData(Guid? id)
{
// Find the data row and return the edit form
Model input = await db.Models.FindAsync(id);
return PartialView("EditModel", input);
}
[HttpPost, ValidateAntiForgeryToken]
public async Task<ActionResult> MyEditAction([Bind(Include =
"Id,Fields,...")] ModelView input)
{
if (TryValidateModel(input))
{
// save changes, return new data row
// status code is something in 200-range
db.Entry(input).State = EntityState.Modified;
await db.SaveChangesAsync();
return PartialView("dataRowView", (ModelView)input);
}
// set the "error status code" that will redisplay the modal
Response.StatusCode = 400;
// and return the edit form, that will be displayed as a
// modal again - including the modelstate errors!
return PartialView("EditModel", (Model)input);
}
This way, if an error occurs while editing Model data in a modal window, the error will be displayed in the modal with validationsummary methods of MVC; but if changes were committed successfully, the modified data table will be displayed and the modal window disappears.
Note: you get ajaxoptions working, you need to tell your bundles configuration to bind jquery.unobtrusive-ajax.js (may be installed by NuGet):
bundles.Add(new ScriptBundle("~/bundles/jqueryajax").Include(
"~/Scripts/jquery.unobtrusive-ajax.js"));
In $('.editor-container').click(function (){}), shouldn't var url = "/area/controller/MyEditAction"; be var url = "/area/controller/EditPartData";?

Data Annotation doesn't not work if control generates from List View Model

I have below view model
public class QuestionarrieAnswersViewModel
{
public long QuestionID { get; set; }
public string Question { get; set; }
[Required(ErrorMessage="required")]
[StringLength(255, ErrorMessage = "Maximum 255 characters are allowed.")]
public string Answer { get; set; }
}
and i am generating view in below way
#model List<BusinessLayer.Models.ViewModel.QuestionarrieAnswersViewModel>
#using (Ajax.BeginForm("SaveQuestionarrie", "Member", FormMethod.Post, new AjaxOptions { OnBegin = "OnBegin", OnComplete = "OnComplete" }, new { #class = "form-horizontal" }))
{
for(int i=0;i<Model.Count;i++)
{
<div class="control-group">
<div class="head_form">
<label class="control-label">#Model[i].Question</label>
<div class="controls">
#Html.TextAreaFor(m=>m[i].Answer)
#Html.ValidationMessageFor(m => m[i].Answer)
#Html.HiddenFor(m=>m[i].QuestionID)
</div>
</div>
</div>
}
<div class="control-group">
<div class="controls">
<button class="btn" type="submit">Save</button>
</div>
</div>
}
I have set dataannotation on Answer field in above model but its not applying in above view while it works if i generate view in below way
#model BusinessLayer.Models.ViewModel.QuestionarrieAnswersViewModel
#using (Ajax.BeginForm("SaveQuestionarrie", "Member", FormMethod.Post, new AjaxOptions { OnBegin = "OnBegin", OnComplete = "OnComplete" }, new { #class = "form-horizontal" }))
{
#Html.TextAreaFor(m => m.Answer)
#Html.TextAreaFor(m => m.QuestionID)
<div class="control-group">
<div class="controls">
<button class="btn" type="submit">Save</button>
</div>
</div>
}
What's going wrong here...
In order to fire those validation rules, you'll need to use an EditorFor instead of a TextAreaFor.
It's because there's an outstanding issue with validation of TextArea's, see here: http://aspnet.codeplex.com/workitem/8576.
This is due to a bug in the version of jquery.validate.unobtrusive.js that was released with ASP.NET MVC3. This answer is on the same bug, the solution to this is to upgrade to the latest version of jquery.validate.unobtrusive.js - either grab it from an MVC4 project or update using NuGet.
The jquery.validate.unobtrusive.js script doesn't seem to have a version number so if you search in the script for a function called escapeAttributeValue, then this is a version of the script that has this bug fix.
The problem that is addressed in the bug fix is how to handle markup generated having name attributes containing characters that need escaping in a jQuery selector. In this case
<textarea cols="20" name="[0].Answer" rows="2"></textarea>
needs this selector
$('[name=\\[0\\]\\.Answer]')
The client-side DataAnnotation (validation) does not work for the Html.TextAreaFor() helper.
To make it work, you have to decorate the 'Answer' property with the [DataType(DataType.MultilineText)] attribute. And in the view, use Html.EditorFor() helper instead of the Html.TextAreaFor() helper mehthod.
See similar SO answer asp.net mvc TextAreaFor is not getting validated as a required field.

Categories