Razor Pages Model Binding in Partial View after post form - ajax

I trying to achieve to following: let's say I have a Create VM form. This is all very standard with stuff:
Model
[BindProperty]
public NewVM NewVM { get; set; }
NewVM Model (part of):
public string Name { get; set; }
public int SelectedTemplateId { get; set; }
public List<Templates> Templates { get; set; }
Template Model (part of):
public int Id{ get; set; }
public string Name { get; set; }
Form (part of)
<form method="post">
<div class="form-group row">
<label for="vmname" class="col-sm-4 col-form-label col-form-label-sm">Virtual Machine Name</label>
<div class="col-sm-8">
<input type="text" class="form-control form-control-sm">
</div>
</div>
</form>
A part of the Create VM form is selecting a template. This is dependent of the Cluster selected. This a presented as a dropdown box
<div class="form-group row">
<label class="col-sm-4 col-form-label col-form-label-sm">Cluster</label>
<div class="col-sm-8">
#Html.DropDownListFor(x => Model.ClusterId, new SelectList(Model.Clusters, "Id", "Name"), "Please select", htmlAttributes: new { #class = "form-control form-control-sm", id = "clusters" })
#Html.ValidationMessageFor(x => x.Tenant.ClusterId, "", new { #class = "text-danger" })
</div>
</div>
So far so good. When a cluster is selected the following Ajax call is made to retrieve the templates available for that cluster:
Javascript:
$(function () {
$("#clusters").change(function () {
var host = $(this).val();
$.ajax({
type: "POST",
url: "/VM/AddVmDC2?handler=GetTemplates",
beforeSend: function (xhr) {
xhr.setRequestHeader("XSRF-TOKEN",
$('input:hidden[name="__RequestVerificationToken"]').val());
},
data: { "host": host },
success: function (response) {
$("#templatebody").html(response);
},
failure: function (response) {
alert(response.responseText);
},
error: function (response) {
alert(response.responseText);
}
});
});
});
Backend
public async Task<IActionResult> OnPostGetTemplates(int clusterid)
{
_apilocation = _config["apiurl"];
var templates = await helper.QueryApi(_apilocation, "/api/inventory/template?clusterid=" + clusterid);
NewVM.Templates = JsonConvert.DeserializeObject<List<Templates>>(templates);
return Partial("_Templates", Tenant);
}
Partial View
#model NewVM
#Html.DropDownListFor(x => x.SelectedTemplateId , new SelectList(Model.Templates, "Id", "Name"), "Please select", htmlAttributes: new { #class = "form-control form-control-sm", id = "templates" })
#Html.ValidationMessageFor(x => x.SelectedTemplateId , "", new { #class = "text-danger" })
Partial view is called in Form
<div class="form-group row">
<label class="col-sm-4 col-form-label col-form-label-sm">Templates</label>
<div class="col-sm-8" id="templatebody">
#{
<partial name="_Templates" for="NewVM"/>
}
</div>
</div>
The problem is that when I post the form SelectedTemplateId is always null. What am I doing wrong here. Can someone help me explain why SelectedTemplateId is always null.
Thanks !

You ajax data is
data: { "host": host },
and your handler is
public async Task<IActionResult> OnPostGetTemplates(int clusterid)
you need to change int clusterid to int host,so that you can get the data in ajax.
And if your SelectedTemplateId is still null,can you share your Tenant structure,and check the SelectedTemplateId in it.

Related

ModelState.AddModelError for Ajax loaded partials

I have a partial view on my main page that loads a form, the model is below:
public class CreateRequestViewModel
{
[Required]
public short ClientId { get; set; }
[Required]
public Guid SystemId { get; set; }
[Required]
public string RequestedUsername { get; set; }
public string TicketReference { get; set; }
public string Notes { get; set; }
public List<SelectListItem> Clients { get; set; }
public List<SelectListItem> Systems { get; set; }
}
This is the partial view:
#model Models.CreateRequestViewModel
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="row">
<div class="col-lg-4 col-md-4 col-sm-12">
<h1>Create a Request</h1>
</div>
<div class="col-lg-8 col-md-8 col-sm-12 right">
<div class="form-group">
#Html.DropDownListFor(m => m.ClientId, Model.Clients, htmlAttributes: new { #class = "form-control form-control-lg", #id = "ClientSelect" })
#Html.ValidationMessageFor(m => m.ClientId, "", htmlAttributes: new { #class = "text-danger" })
</div>
<div class="form-group">
#Html.DropDownListFor(m => m.SystemId, Model.Systems, htmlAttributes: new { #class = "form-control form-control-lg", #id = "ClientSystemSelect" })
#Html.ValidationMessageFor(m => m.SystemId, "", htmlAttributes: new { #class = "text-danger" })
</div>
<div class="form-group">
#Html.TextBoxFor(m => m.RequestedUsername, htmlAttributes: new { #class = "form-control form-control-lg", #placeholder = "Username" })
#Html.ValidationMessageFor(m => m.RequestedUsername, "", htmlAttributes: new { #class = "text-danger" })
</div>
<div class="form-group">
#Html.TextBoxFor(m => m.TicketReference, htmlAttributes: new { #class = "form-control form-control-lg", #placeholder = "Ticket reference" })
</div>
<div class="form-group">
#Html.TextAreaFor(m => m.Notes, htmlAttributes: new { #class = "form-control form-control-lg", #rows = 3, #placeholder = "Notes..." })
</div>
<input type="Submit" class="btn btn-secondary btn-block send-request" value="Submit" name="">
</div>
</div>
This is how I'm loading the page:
<div class="container">
<div class="row">
<div class="col-lg-6">
<form asp-action="CreateRequest" asp-controller="Access"
data-ajax="true"
data-ajax-method="POST"
data-ajax-mode="replace"
data-ajax-update="#createRequest">
<div id="createRequest">
#await Html.PartialAsync("_CreateRequest", Model.CreateRequestModel)
</div>
</form>
</div>
</div>
</div>
With the model as it is and using the unobtrusive javascript, leaving the RequestedUsername blank for example will result in the form not being submitted and a validation message appearing for it. This is great.
However, I have a requirement to check the form data against entries in a database first and throw an error if there's an existing record. I thought that, with all the client side validation passing, I'd use ModelState.AddModelError in the controller like so:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult CreateRequest(CreateRequestViewModel model)
{
if(model.RequestedUsername == "someincorrectvalue"){ //actual logic removed for brevity
ModelState.AddModelError("RequestedUsername", "Already in use");
}
if(!ModelState.IsValid)
{
//reset lists on model, removed
return PartialView("_CreateRequest", model);
}
_logger.LogInformation("CreateRequest successful");
return RedirectToAction(nameof(Index));
}
However, if I use ModelState.AddModelError, the return PartialView("_CreateRequest", model) call ends up reloading the whole page as if it's returning a full View.
I'm at a loss as to why this is happening. The difference I can see is that i'm adding a ModelState error inside the controller, whereas validation is happening client side otherwise.
Anyone have an idea?
So, this turned out to be a combination of problems. To begin with, the unobtrusive Ajax scripts I had within my solution were not performing. I don't know why but I replaced them with one from the CDN: https://ajax.aspnetcdn.com/ajax/jquery.unobtrusive-ajax/3.2.5/jquery.unobtrusive-ajax.min.js
That solved the problem of the whole page reloading instead of the partial being returned via unobtrusive ajax.
The second problem was that, on success, I was redirecting to the Index controller action instead of returning a Partial again. This was causing the entire Index page to be rendered inside the div i'd chosen as my ajax target. My controller action now looks like this:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult CreateRequest(CreateRequestViewModel model)
{
if(model.RequestedUsername == "someincorrectvalue"){ //actual logic removed for brevity
ModelState.AddModelError("RequestedUsername", "Already in use");
}
if(!ModelState.IsValid)
{
//reset lists on model, removed
return PartialView("_CreateRequest", model);
}
_logger.LogInformation("CreateRequest successful");
// reset lists on model, removed
ModelState.Clear(); // get rid ofany model details to make way for a new request
return PartialView("_CreateRequest", model);
}

Partial View not printing values in mvc 5

I'm using ajax call to execute partial view controller action to print max batch number. Everything executes, but the value doesn't print in partial view. On selected dropdown list I'm calling ajax method to execute. Below is my code that i'm using.
Models.Batch.cs
[Required]
[StringLength(100)]
[DataType(DataType.Text)]
[Display(Name = "Batch Number")]
public string BatchNumber { get; set; }
BatchController.cs
public PartialViewResult GetBatchNumber(string CourseID)
{
Context.Batches contBatch = new Context.Batches();
Models.Batches b = new Models.Batches();
b.BatchNumber = contBatch.GetallBatchList().ToList().Where(p => p.CourseId == CourseID).Select(p => p.BatchNumber).Max().FirstOrDefault().ToString();
ViewBag.Message = b.BatchNumber;
return PartialView(b);
}
CreateBatch.cshtml
<div class="col-md-10">
#Html.DropDownListFor(m => m.CourseId, Model.Courses, new { id = "ddlCourse" })
<div id="partialDiv">
#{
Html.RenderPartial("GetBatchNumber", Model);
}
</div>
<script>
$("#ddlCourse").on('change', function () {
var selValue = $('#ddlCourse').val();
$.ajax({
type: "GET",
url: '/Batch/GetBatchNumber?CourseId=' + selValue,
dataType: "json",
data: "",
success: function (data) {
$("#partialDiv").html(data);
},
failure: function (data) {
alert('oops something went wrong');
}
});
});
</script>
</div>
GetBatchNumber.cshtml
#model WebApplication1.Models.Batches
<div class="form-group">
#Html.LabelFor(model => model.BatchNumber, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DisplayFor(model => model.BatchNumber)
#Html.ValidationMessageFor(model => model.BatchNumber, "", new { #class = "text-danger" })
</div>
</div>

Ajax Post: ASP.NET MVC action is receiving empty object

I have this form:
#model CupCakeUI.Models.CupCakeEditViewModel
#using (Html.BeginForm(null, null, FormMethod.Post, new { id = "createFrm" }))
{
<div class="form-group">
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
<input type="text" id="Name" name="Name" value="#Model.Name" />
</div>
<div class="editor-label">
#Html.LabelFor(model => model)
</div>
<div class="editor-field">
<input type="text" id="Price" name="Price" value="#Model.Price" />
</div>
<div class="col-md-offset-2 col-md-10">
<input type="button" id="btnCreate" value="Create" class="btn btn-default" />
</div>
</div>
}
I am trying to use ajax post to send data to the Action Method, however its always receiving empty object. I have done that several times in the past, and now i tried different ways which not working, The code:
$(document).ready(function () {
$("#btnCreate").click(function () {
var name = $("#Name").val();
var price = $("#Price").val();
var cupCakeEditModel = { "CupCakeId": 0, "Name": name, "Price": price };
var json = JSON.stringify(cupCakeEditModel);
$.ajax({
type: 'POST',
url: "/CupCake/Create",
data: JSON.stringify(cupCakeEditModel),
contentType: 'application/json',
success: function () {
alert("succes");
},
error: function () {
alert("error");
}
});
})
})
Its showing this in the console when logging:
This is the Action Method and Class used:
[HttpPost]
public JsonResult Create (CupCakeUI.Models.CupCakeEditViewModel cupCakeEditModel)
{
var cupCake =
CupCakeData.Save(cupCakeEditModel);
return Json("cupCake",
JsonRequestBehavior.AllowGet);
}
This the class:
public class CupCakeEditViewModel
{
public int CupCakeId;
[Display(Name = "CupCake Name")]
public string Name;
public string Price;
}
I have also used this, but not working:
$("#btnCreate").click(function () {
var cupCakeEditModel =
$("#createFrm").serialize();
$.ajax({
url: "/CupCake/Create",
type: "POST",
data: cupCakeEditModel,
success: function (response) {
alert("Success");
},
error: function (response) {
}
});
})
And several answers i found on the forum, but it seems something weird!
You model contains only fields, and the DefaultModelBinder does not bind fields, only properties. Change the model to
public class CupCakeEditViewModel
{
public int CupCakeId { get; set; }
[Display(Name = "CupCake Name")]
public string Name { get; set; }
public string Price { get; set; }
}

Unobtrusive client validation is not working when the form contains hidden input field

I have a model like:
public class Person
{
public int ID { get; set; }
[Required]
public string LastName { get; set; }
[Required]
public string FirstMidName { get; set; }
}
And the view:
#model ContosoUniversity.Models.Student
#using (Html.BeginForm("Edit", "Student", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.HiddenFor(model => model.ID)
<div
#Html.LabelFor(model => model.LastName, new { #class = "control-label col-md-2" })
#Html.EditorFor(model => model.LastName)
#Html.ValidationMessageFor(model => model.LastName)
</div>
<div >
#Html.LabelFor(model => model.FirstMidName, new { #class = "control-label col-md-2" })
#Html.EditorFor(model => model.FirstMidName)
#Html.ValidationMessageFor(model => model.FirstMidName)
</div>
<div>
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
}
The problem is that when I am rendering the view using Ajax Get using return PartialView("Edit", student); statement, the unobtrusive jQuery validation fails. The validation is working fine when I remove the hidden field #Html.HiddenFor(model => model.ID).
I have used below code to render the view using Ajax Get request:
function ShowEditPartialView(sender) {
$.ajax({
type: "GET",
url: "/Student/EditPartial",
data: {
id: courseid
},
async: false,
success: function (data) {
$("#EditCoursePartial").html(data);
///because the page is loaded with ajax, the validation rules are lost, we have to rebind them:
$('form').removeData('validator');
$('form').removeData('unobtrusiveValidation');
$.validator.unobtrusive.parse('form');
}
});
}
Any idea on how it will be resolved.

jQuery UI Datepicker not Posting to Server after Deployment

So I published a ASP.Net MVC3 project that uses the jQuery UI datepickers on to a IIS server. The datepickers don't seem to post their values and revert to default values on the back end.
Locally, though it works like a charm. It's simple jQuery without any options on the datepicker.
Any clue as to why that would happen?
Let me know what I can post to help find a solution.
Thanks!
The model I am trying to post back:
public class Report
{
[Required]
[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
public DateTime From { get; set; }
[Required]
[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
public DateTime To { get; set; }
public virtual Car Car { get; set; }
public virtual IEnumerable<Review> Reviews { get; set; }
}
The form I am using:
#model Cars.Models.Report
<h3>Create a report</h3>
#using (Html.BeginForm("Generate", "Report"))
{
<div>
<div class="span-5">
<div class="editor-label">
#Html.LabelFor(model => model.From)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.From, new { #class = "datepicker lilbig" })
</div>
</div>
<div class="span-5 last">
<div class="editor-label">
#Html.LabelFor(model => model.To)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.To, new { #class = "datepicker lilbig" })
</div>
</div>
<div class="span-11 last">
<div class="prepend-5 last">
<input class="bigbtn" type="submit" value="Create" />
</div>
<div>
#Html.ValidationSummary(true)
#Html.ValidationMessageFor(model => model.From)
#Html.ValidationMessageFor(model => model.To)
</div>
</div>
</div>
}
Method I am posting to:
[HttpPost]
public ActionResult Generate(Report model)
{
try
{
MembershipUser currentUser = Membership.GetUser(HttpContext.User.Identity.Name);
Guid id = (Guid)currentUser.ProviderUserKey;
Car currentCar = CarRepository.Get(id);
currentCar.LastReportCreated = DateTime.Now;
currentCar = CarRepository.Update(currentCar, true);
model.Car = currentCar;
model.Reviews = model.Car.Reviews.Where(s => s.LoggedAt.Date >= model.From.Date &&
s.LoggedAt.Date <= model.To.Date);
return View("Report", model);
}
catch(Exception ex)
{
return View("Error");
}
}
The jQuery looks like this:
$(document).ready(function () {
$(".datepicker").datepicker();
});
Check that the datepicker version on the server is current and running bug-free. Also double check your form data before it gets posted - make sure the date fields have values:
$(document).ready(function () {
$("form").submit(function(){
$(".datepicker").each(function(){
alert( $(this).attr("name") +": "+ $(this).value() );
});
});
});

Resources