Ajax.Beginform + Partial view + Model state are not working together - asp.net

I have the following Create action method inside my ASP .NET MVC :-
public ActionResult CreateVMNetwork(int vmid)
{
VMAssignIps vmips = new VMAssignIps()
{
TechnologyIP = new TechnologyIP() { TechnologyID = vmid},
IsTMSIPUnique = true,
IsTMSMACUnique = true
};
return PartialView("_CreateVMNetwork",vmips);
}
Which will render the following partial view:-
#model TMS.ViewModels.VMAssignIps
#using (Ajax.BeginForm("CreateVMNetwork", "VirtualMachine", new AjaxOptions
{
InsertionMode = InsertionMode.InsertAfter,
UpdateTargetId = "networktable",
LoadingElementId = "loadingimag",
HttpMethod= "POST"
}))
{
#Html.ValidationSummary(true)
#Html.HiddenFor(model=>model.TechnologyIP.TechnologyID)
<div>
<span class="f">IP Address</span>
#Html.EditorFor(model => model.TechnologyIP.IPAddress)
#Html.ValidationMessageFor(model => model.TechnologyIP.IPAddress)
<input type="CheckBox" name="IsTMSIPUnique" value="true" #(Html.Raw(Model.IsTMSMACUnique ? "checked=\"checked\"" : "")) /> |
<span class="f"> MAC Address</span>
#Html.EditorFor(model => model.TechnologyIP.MACAddress)
#Html.ValidationMessageFor(model => model.TechnologyIP.MACAddress)
<input type="CheckBox" name="IsTMSMACUnique" value="true" #(Html.Raw(Model.IsTMSMACUnique ? "checked=\"checked\"" : "")) />
</div>
<input type="submit" value="Save" class="btn btn-primary"/>
}
Inside the following main view, after clicking on the Ajax.actionlink:-
#Ajax.ActionLink("Add Network Info", "CreateVMNetwork","VirtualMachine",
new { vmid = Model.VirtualMachine.TMSVirtualMachineID },
new AjaxOptions {
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "AssignNetwork" ,
LoadingElementId = "progress"
}
)
</p>
<p><img src="~/Content/Ajax-loader-bar.gif" class="loadingimage" id="progress" /></p>
<div id ="AssignNetwork"></div>
Then when clicking on the “Save” button it will call the following action method:-
[HttpPost]
public ActionResult CreateVMNetwork(VMAssignIps vmip)
{
if (ModelState.IsValid)
{
try
{
repository.InsertOrUpdateVMIPs(vmip.TechnologyIP,User.Identity.Name);
repository.Save();
return PartialView("_networkrow",vmip);
}
catch (Exception ex)
{
ModelState.AddModelError(string.Empty, "Error occurred: " + ex.InnerException.Message);
}}
return PartialView("_CreateVMNetwork", vmip);
}
When will render the following partial view _networkrow:-
#model TMS.ViewModels.VMAssignIps
<tr id="#Model.TechnologyIP.ID">
<td> #Model.TechnologyIP.IPAddress</td>
<td>#Model.TechnologyIP.MACAddress</td>
<td>#Ajax.ActionLink("Delete",
"DeleteNetworkInfo", "VirtualMachine",
new { id = Model.TechnologyIP.ID },
new AjaxOptions
{ Confirm = "Are You sure You want to delete (" + Model.TechnologyIP.IPAddress + ")" + "( " + Model.TechnologyIP.MACAddress + ").",
HttpMethod = "Post",
OnSuccess = "deletionconfirmation",
OnFailure = "deletionerror"
})</td>
</tr>
All the above will work fine unless a model state error or an exception occurred , then in this case the table will be updated with the partial view and the model state will be displayed under the table with the fields. But I need to display the model state error on the same original view. So that I need the Ajax.begin form to update the table only if no exception or model state errors occured, and to display an error message inside the original partial view, not under the table.
Can anyone advice on how to solve this issue?

I don't get your question clearly, however I think you should place a <div> into your view where you want to show errors.
Then, if you got some errors on your processing, push some error messages through ViewBag to the model.
So, your action method will become like this:
[HttpPost]
public ActionResult CreateVMNetwork(VMAssignIps vmip)
{
if (ModelState.IsValid)
{
try
{
repository.InsertOrUpdateVMIPs(vmip.TechnologyIP,User.Identity.Name);
repository.Save();
return PartialView("_networkrow",vmip);
}
catch (Exception ex)
{
ViewBag.ErrorMessage = "Some messages...";
// also any other info you like
}
}
return PartialView("_CreateVMNetwork", vmip);
}
And that in your view like this:
<div>
<p>#ViewBag.ErrorMessage</p>
</div>
So, if you you got some errors, they'll be shown in that

Where is networktable defined? Remember that even if there is an exception (since you have caught it) or ModelState error, the ajax response will come back with status 200 and the OnSuccess will be executed. In the case of the save button, the returned response from the server will be inserted after the html of whatever networktable contains already. If you want special action to be taken when there is a failure, you have to change the response status on the server and catch it through a function on the client side called by OnFailure on the ajax form.

Related

ASP.NET MVC – Use Html.BeginForm to pass model data and non-model hidden field value

I’m using BeginForm to pass model data (i.e. last_coffe_time) which works fine. However I also wanted to pass to controller ClientDateTime value stored in hidden field which is not a part of the model.
I don’t know how do do it, at best I can pass ClientDateTime as static string but I can't figure out how to dynamically read hidden field for value and pass that value to controller.
Someone please help. Thank you
View:
#model SleepNotes.Models.Diary
#using (Html.BeginForm("Edit", "Diary", new { ClientDateTime = "ClientDateTime" }, FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.Hidden("ClientDateTime", new { id = "ClientDateTime" })
<div class="form-group">
#Html.LabelFor(model => model.last_coffe_time, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.last_coffe_time, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.last_coffe_time, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
<!--Set ClientDateTime-->
<script>
var a = new Date()
var month = a.getMonth() + 1;
document.getElementById('ClientDateTime').value = a.getDate() + "/" + month + "/" + a.getFullYear() + " " + a.getHours() + ":" + a.getMinutes() + ":" + a.getSeconds();
</script>
Controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "id,last_coffe_time")] Diary Diary, string ClientDateTime)
{
if (ModelState.IsValid)
{
//ClientDateTime from view ...do something here
db.Entry(Diary).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index", "Dashboard");
}
return View(Diary);
}
Remove the new { ClientDateTime = "ClientDateTime" } from the BeginForm() method so the vale is not added as a route parameter (and therefore not bound to your string ClientDateTime parameter in the Edit() method.
Side note: since your wanting to submit a DateTime value, your parameter should be DateTime ClientDateTime (not string) and your script can be simplified to
document.getElementById('ClientDateTime').value = new Date().toISOString();
There are some solutions,
I suggest you to have a ModelView between your Model and View. That
modalview should have the ClientDateTime.
You can use ajax post and send 2 parameters (yourmodal, string) to controller or you
can pass the ClientDateTime as querystring. There are lots of
examples about it.
Hope helps.

MVC 5 - two separate models on a page, how do I attach one to a form so I can submit it?

TL;DR: How do I handle form data that is being submitted with nonstandard names for the data?
The stats:
MVC 5
ASP.NET 4.5.2
I am bringing in two different models:
public async Task<ActionResult> Index() {
var prospectingId = new Guid(User.GetClaimValue("CWD-Prospect"));
var cycleId = new Guid(User.GetClaimValue("CWD-Cycle"));
var viewModel = new OnboardingViewModel();
viewModel.Prospecting = await db.Prospecting.FindAsync(prospectingId);
viewModel.Cycle = await db.Cycle.FindAsync(cycleId);
return View(viewModel);
}
One called Prospecting, the other called Cycle. The Prospecting one is working just fine, as nothing else on the page needs it except one small item.
The Cycle has a mess of separate forms on the page, each needing to be separately submittable, and editing just one small part of the Cycle table. My problem is, I don't know how to submit the correct data to the backend. I am also not entirely sure how to "catch" that data.
The bright spot is that apparently the front end is properly reflective of what is in the db. As in, if I manually change the db field to a true value, the checkbox ends up being selected on refresh.
My current form is such:
#using(Html.BeginForm("UpdatePDFResourceRequest", "Onboarding", FormMethod.Post, new { enctype = "multipart/form-data" })) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<fieldset>
#Html.LabelFor(Model => Model.Cycle.PDFResourceLibrary, htmlAttributes: new { #class = "control-label" })
#Html.CheckBoxFor(Model => Model.Cycle.PDFResourceLibrary, new { #class = "form-control" })
#Html.ValidationMessageFor(Model => Model.Cycle.PdfResourceLibrary, "", new { #class = "text-danger" })
<label class="control-label"> </label><button type="submit" value="Save" title="Save" class="btn btn-primary glyphicon glyphicon-floppy-disk"></button>
</fieldset>
}
But the resulting HTML is such:
<input id="Cycle_PDFResourceLibrary" class="form-control" type="checkbox" value="true" name="Cycle.PDFResourceLibrary" data-val-required="'P D F Resource Library' must not be empty." data-val="true">
As you can see, the name= is Cycle.PDFResourceLibrary and I don't know how to catch this on the backend.
My model for that specific form is:
public class PDFResourceRequestViewModel {
[DisplayName("PDF Resource Library Request")]
public bool PDFResourceLibrary { get; set; }
[DisplayName("Date Requested")]
[DataType(DataType.Date)]
public DateTime PDFResourceLibraryDate { get; set; }
[DisplayName("Notes")]
public string PDFResourceLibraryNotes { get; set; }
}
(not the overall model for that table, though)
And the method used to handle the form submission is:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> UpdatePDFResourceRequest(PDFResourceRequestViewModel model) {
var id = new Guid(User.GetClaimValue("CWD-Cycle"));
Cycle cycle = await db.Cycle.FindAsync(id);
if(cycle == null) {
return HttpNotFound();
}
try {
cycle.CycleId = id;
cycle.PDFResourceLibrary = model.PDFResourceLibrary;
cycle.PDFResourceLibraryDate = DateTime.Now;
cycle.PDFResourceLibraryNotes = model.PDFResourceLibraryNotes;
db.Cycle.Add(cycle);
await db.SaveChangesAsync();
return RedirectToAction("Index");
} catch { }
return View(model);
}
Now, I know that the method is wrong, for one I am editing just three values out of dozens in that table, so I need to be using something like this method. Problem is, the form is getting submitted with the name= of Cycle.PDFResourceLibrary and it is not being matched up on the back end.
Help?
You can use the [Bind(Prefix="Cycle")] attribute to 'strip' the prefix so that name="Cycle.PDFResourceLibrary" effectively becomes name="PDFResourceLibrary" and will bind to your PDFResourceRequestViewModel
public async Task<ActionResult> UpdatePDFResourceRequest([Bind(Prefix="Cycle")]PDFResourceRequestViewModel model)

Ajax.beginform with captcha fails when captcha input is invalid

Im using CaptchaMvc.Mvc4 1.5.0 in a form with #Ajax.BeginForm in my Asp.net MVC4 application. when input is valid it works fine. but when captcha input is invalid. dispose method is called and i cant resubmit the message. and the captcha image dosnt change.
This is my View:
#using CaptchaMvc.HtmlHelpers;
#using CaptchaMvc;
#model Web.Models.Message
#using (Ajax.BeginForm("Contact", "Home", new AjaxOptions
{
HttpMethod = "post",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "Sent"
}))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
#Html.LabelFor(model => model.Message1)
#Html.TextBoxFor(model => model.Message1)
#Html.ValidationMessageFor(model => model.Message1)
#Html.MathCaptcha()
<input type="submit" value="Create" />
</fieldset>
<div id="Sent">
</div>
}
And this is my Action method:
[HttpPost]
public ActionResult Contact(Message message)
{
if(this.IsCaptchaValid("error"))
if (ModelState.IsValid )
{
db.Messages.Add(message);
db.SaveChanges();
return RedirectToAction("SuccessfullySent");
}
ModelState.AddModelError("", "there is some error");
return Content("there is some error");
}
ps: I tested it with #htmal.Beginform and it worked fine. some how problem is related to #Ajax.Beginform.
the trick is create a partial view and put code for generate captcha on it :
#using CaptchaMvc.HtmlHelpers
#using CaptchaMvc;
<div>
#Html.MathCaptcha()
</div>
<div>
#ViewBag.CaptchaInvalid
</div>
<div>
#ViewBag.Successfully
</div>
and in the main form where the ajax.beginform is put rendering this parcial view on the updateTargetId div "Sent":
<div id="Sent">
#{Html.RenderPartial("~/Views/Shared/CaptchaGenerate.cshtml");}
</div>
and in the action method make this changes:
[HttpPost]
public ActionResult Contact(Message message)
{
if(this.IsCaptchaValid("error"))
if (ModelState.IsValid )
{
db.Messages.Add(message);
db.SaveChanges();
ViewBag.CaptchaInvalid = "";
ViewBag.Successfully = "successfully sent";
return PartialView("~/Views/Shared/CaptchaGenerate.cshtml");
}
ModelState.AddModelError("", "there is some error");
ViewBag.CaptchaInvalid = "";
ViewBag.Successfully = "";
return PartialView("~/Views/Shared/CaptchaGenerate.cshtml");
}

How to pass model from one partial view to another partial view

I have a model called Result. Suppose this has 4 fields, like student_id, marks, status, remarks.
Now I have a view in which student listing is shown. In front of each student, there is a button for enter marks of exam. On clicking on button a pop-up will open and there will be 2 fields student_id, marks and 2 buttons 'pass' and 'fail'.
On clicking on fail button another pop-up will appear for enter remarks only.
Now my question is that, how can I retain values of first pop-up on second pop-up, As on clicking on 'submit' button of second pop-up, I will save all the details.
I know a way to do this using hidden fields in second pop-up. Is there any other way to do this?
Model classes are:
1. User (id, name, f_name, address...)
2. Result (student_id, marks, grade, remarks)
Student List view
#{
List<User> Student = (List<User>)ViewData["Student"];
}
<table id="table_id">
<tr>
<th class="dbtc">S.No.</th>
<th class="dbtc">Student Name)</th>
<th style="width: 110px">Operate</th>
</tr>
#foreach (User usr in Student)
{
int index = Student.IndexOf(usr);
<tr>
<td class="dbtc">
#(Student.ToList().IndexOf(usr) + 1)
</td>
<td>
#Html.ActionLink(usr.FirstName + " " + usr.LastName, "Details", "User", new { id = usr.Id }, null)
</td>
<td>
#Ajax.ActionLink("Examine", "Result", new { id = Model.Id, userId = usr.Id }, new AjaxOptions
{
HttpMethod = "GET",
UpdateTargetId = "divPopup",
InsertionMode = InsertionMode.Replace,
OnSuccess = "openPopup('Examine Content')"
})
</td>
</tr>
First Partial view of examine
#model ComiValve.Models.Result
#using (Html.BeginForm("ExamPass", "Student", new { #id = (int)ViewBag.id, userId = (int)ViewData["UserId"] }, FormMethod.Post))
{
<div id="divExamAdvice"></div>
<div class="editor-label">
#Html.DisplayNameFor(model => model.Name)
</div>
<div class="editor-field">
#Html.TextAreaFor(model => model.Marks)
</div>
<div class="editor-field">
#Html.TextAreaFor(model => model.Grade)
</div>
<div class="login_submit_div">
<input type="submit" value="Pass" />
#Ajax.ActionLink("Fail", "ExamAdvice", new { id = (int)ViewBag.id, userId = (int)ViewData["UserId"] }, new AjaxOptions
{
HttpMethod = "GET",
UpdateTargetId = "divPopup",
OnSuccess = "openPopup('Exam Advice')"
})
</div>
}
Second partial view for remaks (when user click on fail, then this view will open.)
#model ComiValve.Models.ExamContent
#using (Html.BeginForm("ExamFail", "Student", new { id = Model.id }, FormMethod.Post))
{
<div id="divExamAdvice"></div>
<div class="editor-label">
#Html.DisplayNameFor(model => model.Remarks)
</div>
<div class="editor-field">
#Html.TextAreaFor(model => model.Remarks)
</div>
<div class="left">
<input type="submit" value="Confirm Fail" />
</div>
}
Methods of Controller
public virtual ActionResult ExamContent(int id, int userId)
{
ViewBag.IsApprove = true;
ViewBag.UserId = userId;
ViewBag.id = id;
return PartialView("ExamContent");
}
public virtual ActionResult ExamAdvice(int id, int userId)
{
ViewBag.IsApprove = true;
if (Request.IsAjaxRequest())
{
Result result = new Result();
result.id = id;
result.User = db.Users.Find(userId);
return PartialView("ExamAdvice", result);
}
else
{
return RedirectToAction("Index");
}
}
Why are you passing the model between partial views. You can create a single Model and use it on both the views. In case of having two different tables, create the two different "Lists" of "Table" type. Like this
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;
using System.Web.Mvc;
namespace LearningMVCapp.Models
{
public class Data
{
public List<tbl_Dept> lstDepatrment;
public List<tbl_employees> lstEmployees;
//other properties
}
}
You can also use session instead of hidden fields, refer this link http://www.dotnet-tricks.com/Tutorial/mvc/906b060113-Controlling-Session-Behavior-in-Asp.Net-MVC4.html.

Model values are null during [HttpPost]

I'm having some problems with my code and was hoping someone could give me a hand. Here's the snippet I'm working with:
[Authorize]
public ActionResult EventResults(int id)
{
List<Event> CompetitionEvents = Event.getEventsByCompetitionId(id);
ViewBag.CompetitionEvents = CompetitionEvents;
List<Person> Competitors = Competition.getCompetitorsByCompetitionID(id);
ViewBag.Competitors = Competitors;
List<Results> Results = Competition.getCompetitorResultsPairings(CompetitionEvents, Competitors);
ViewBag.Results = Results;
ViewBag.OrganizerEmail = Competition.getCompetitionById(id).OrganizerEmail;
return View();
}
#model BINC.Models.Results
#using BINC.Models;
#{
var eventList = ViewBag.CompetitionEvents as List<Event>;
var competitorList = ViewBag.Competitors as List<Person>;
var resultList = ViewBag.Results as List<Results>;
}
<h2></h2>
<p>Results:</p>
#using (Html.BeginForm())
{
foreach (var evt in eventList)
{
<fieldset>
<legend>#evt.activity.Name</legend>
<p>Event Description: #evt.activity.Description</p>
#foreach (var competitor in competitorList)
{
foreach (var result in resultList)
{
if (result.EventID == evt.id && result.CompetitorEmail == competitor.Email)
{
<p>Competitor: #competitor.FirstName #competitor.LastName</p>
<p>Score: #result.Score</p>
if (ViewBag.OrganizerEmail.Equals(#User.Identity.Name))
{
#Html.LabelFor(model => model.Score, "New Score ");
#Html.TextBoxFor(model => model.Score, new { maxlength = 10, style = "width:125px" })
<input type="submit" name="submitButton" value="Update" />
}
}
}
}
</fieldset>
}
}
[HttpPost]
public ActionResult EventResults(Results res)
{
//stuff
}
My problem is nothing other than the score is set on my Results object.
For example, when I put the value '15' into the text box and click 'Update', I'm passing the Result model object to the httppost method, which has everything set to null other than the 'score' field that I just entered.
Am I over complicating this? Is there an easier way?
I tried adding
#Html.HiddenFor(model => model.EventID);
#Html.HiddenFor(model => model.CompetitorEmail);
but that didn't seem to help any.
You are having multiple Submit buttons and that could be the issue, also this is not considered as good practise
<input type="submit" name="submitButton" value="Update" />
keep just one submit button at the end of the form
Basically-- make sure you pass the model to view-- and use the Html Helpers (ie TextBoxFor() and HiddenFor)
I don't think it's an issue with the submit button-- but the one thing that would probably help is to actually pass the model to the view. You are using the ViewBag to pass your data. Pass the model to View and your Html Helpers should generate the correct form names in order for the model binding to work.

Resources