MVC Form Losing Values During Server Side Validation - asp.net-mvc

I am migrating an existing site from WebForms to MVC 5.
I have a Contact Us page that has client-side validation for checking if the fields are completed, etc. However, I am using BotDetect CAPTCHA which has server-side validation to check if the CAPTCHA was completed correctly.
If I enter the correct CAPTCHA, all works as expected -- an email is sent and all the form fields are represented in the email.
If I enter the wrong CAPTCHA, I should be returned to the form, an error message will be displayed for the invalid CAPTCHA but all the other fields should be still filled in.
However, this isn't happening. The form field is lost. I am guessing this is because the Model is getting regenerated. What I haven't figured out is how to get my ActionResult to just abandon doing any actions and leave the form contents in place.
My view:
#using BotDetect.Web.UI.Mvc;
#model Framework.Models.FrameworkModel
#section Styles {
<link href="#BotDetect.Web.CaptchaUrls.Absolute.LayoutStyleSheetUrl" rel="stylesheet" type="text/css" />
}
<h1>#Html.Raw(Model.Page.Heading)</h1>
<div class="main-container">
#if (string.IsNullOrWhiteSpace(ViewBag.Status))
{
#Html.Raw(Model.Page.Body)
<br />
<fieldset id="ContactFields" class="contact-fields">
<ol>
<li>
<label id="FullNameLabel" labelfor="FullName">#Resources.Contact.FullNameLabel</label>
#Html.TextBoxFor(model => model.Contact.FullName, new { #id = "FullName", #Name = "FullName", #class = "standard-textbox", #autocompletetype = "DisplayName", #data_validation_required = Resources.Contact.FullNameValidationErrorMessage})
</li>
<li>
<label id="EmailLabel" labelfor="Email">#Resources.Contact.EmailLabel</label>
#Html.TextBoxFor(model => model.Contact.Email, new { #id = "Email", #Name = "Email", #class = "standard-textbox", #autocompletetype = "Email", #data_validation_required = Resources.Contact.EmailValidationErrorMessage, #data_validation_format = Resources.Contact.EmailValidationErrorMessageFormat })
</li>
<li>
<label id="SubjectLabel" labelfor="Subject">#Resources.Contact.SubjectLabel</label>
#Html.TextBoxFor(model => model.Contact.Subject, new { #id = "Subject", #Name = "Subject", #class = "standard-textbox", #data_validation_required = Resources.Contact.SubjectValidationErrorMessage })
</li>
<li>
<label id="MessageLabel" labelfor="Message">#Resources.Contact.MessageLabel</label>
#Html.TextAreaFor(model => model.Contact.Message, new { #id = "Message", #Name = "Message", #class = "multiline-textbox", #rows = 5, #data_validation_required = Resources.Contact.MessageValidationErrorMessage })
</li>
<li>
<div class="captcha-control">
<br />
#{ MvcCaptcha captcha = Model.CaptchaHelper.GenerateCaptcha(); }
#Html.Captcha(captcha)
<br />
#Html.TextBox("CaptchaCode", "", new { #id = "CaptchaCode", #Name = "CaptchaCode", #class = "captcha-code short-textbox", #data_validation_required = Resources.Captcha.CaptchaValidationErrorMessage })
#Html.ValidationMessage("CaptchaCode")
</div>
<br />
<button id="SendButton" type="submit" class="send-button">#Resources.Contact.SendButton</button>
</ol>
</fieldset>
}
else
{
<span class="alert">#Html.Raw(ViewBag.Status)</span>
}
</div>
#section Scripts {
#Scripts.RenderFormat("<script type=\"text/javascript\" src=\"{0}\" defer=\"defer\"></script>", "~/bundles/scripts/contact")
}
Part of my controller (removed calls to irrelevant actions):
using Framework.App_Code.ViewRendering;
using Framework.Models;
using System.Web.Mvc;
using BotDetect.Web.UI.Mvc;
namespace Framework.Controllers
{
public class PagesController : AsyncController
{
[HttpGet]
[ActionName("Contact")]
public ActionResult Contact()
{
Contact viewRendering = new Contact();
return View(viewRendering.GenerateModel());
}
[HttpPost]
[ActionName("Contact")]
[AllowAnonymous]
[CaptchaValidation("CaptchaCode", "Captcha")]
public ActionResult ContactPost(ContactModel contactModel, bool captchaValid)
{
Contact viewRendering = new Contact();
if (ModelState.IsValid)
{
contactModel.IPAddress = Request.ServerVariables["REMOTE_ADDR"].Trim();
ViewBag.Status = viewRendering.SendMail(contactModel);
}
MvcCaptcha.ResetCaptcha("Captcha");
return View(viewRendering.GenerateModel());
}
}
}

When you return back to the view in the invalid case, pass the model back to have it re-displayed (i.e. when !ModelState.IsValid do this...)
return View(contactModel); // this is the one that was submitted

Related

How can i pass multiple radio button values to controller in ASP.NET MVC?

I've a model that contains 3 tables in my view.
public class InExam
{
public AutoTests TheTest { get; set; }
public List<InTest> TheQuestions { get; set; }
public IEnumerable<Result> SingleQuee { get; set; }
}
First one made to get the detailed page, like "admin/AutoTests/id"
Second one made to get a list of questions linked to the page
Third one is to save radio button strings to post it back into the controller
my plan is to get (say) 20 questions that are linked with the detailed page, Adding 4 radio buttons for each question, and post back every selected button to the controller.
my view form :
#using (Html.BeginForm("Test", "Exams", new { id = Model.TheTest.id }, FormMethod.Post))
{
foreach (var item in Model.TheQuestions)
{
Kafo.Models.Result singleQuee = Model.SingleQuee.Where(x => x.Question == item.Question).FirstOrDefault();
<div class="container" style="padding-top:50px;direction:rtl;">
<h4 style="text-align:right;font-weight:bold;">#item.Question</h4>
<div class="container">
<div class="row" style="direction:rtl;">
<div class="col-lg-7" style="text-align:right;margin-right:10px;">
<div class="row">
#Html.RadioButtonFor(x => singleQuee.Question, new { #class = "form-control dot", #Name = singleQuee.Question, #Value = "1" })
<h5 style="padding-top:3px;padding-right:8px;">#item.RightAnswer</h5>
</div>
</div>
<div class="col-lg-7" style="text-align:right;margin-right:10px;">
<div class="row">
#Html.RadioButtonFor(x => singleQuee.Question, new { #class = "form-control dot", #Name = singleQuee.Question, #Value = "2" })
<h5 style="padding-top:3px;padding-right:8px;">#item.Answer2</h5>
</div>
</div>
<div class="col-lg-7" style="text-align:right;margin-right:10px;">
<div class="row">
#Html.RadioButtonFor(x => singleQuee.Question, new { #class = "form-control dot", #Name = singleQuee.Question, #Value = "3" })
<h5 style="padding-top:3px;padding-right:8px;">#item.Answer3</h5>
</div>
</div>
<div class="col-lg-7" style="text-align:right;margin-right:10px;">
<div class="row">
#Html.RadioButtonFor(x => singleQuee.Question, new { #class = "form-control dot", #Name = singleQuee.Question, #Value = "4" })
<h5 style="padding-top:3px;padding-right:8px;">#item.Answer4</h5>
</div>
</div>
#Html.HiddenFor(m => singleQuee.Question)
</div>
</div>
</div>
}
<button class="btn botton" type="submit" onclick="return confirm('');">END</button>
}
i used this line "Kafo.Models.Result singleQuee = Model.SingleQuee.Where(x => x.Question == item.Question).FirstOrDefault();" in my view because i can't use tuple foreach ( C# ver. 5 )
This is my controller code :
[HttpGet]public ActionResult Test(int? id)
{
using (KafoEntities db = new KafoEntities())
{
InExam model = new InExam();
model.TheTest = db.AutoTests.Where(x => x.id == id).FirstOrDefault();
model.TheQuestions = db.InTest.Where(x => x.UserEmail == currentUser.Email && x.ExamId == model.TheTest.id).OrderByDescending(x => x.id).Take(Convert.ToInt32(model.TheTest.QuestionsNumber)).ToList();
model.SingleQuee = db.Result.ToList();
return View(model);
}
}
[HttpPost]
public ActionResult Test(int? id, List<Result> singleQuee)
{
using (KafoEntities db = new KafoEntities())
{
int result = 0;
foreach (Result item in singleQuee)
{
Result sets = db.Result.Where(x => x.id == item.id).FirstOrDefault();
sets.Question = item.Question;
db.SaveChanges();
var check = db.InTest.Where(x => x.Question == item.Question).FirstOrDefault();
if (check != null)
{
if (item.Question == "1")
{
result++;
}
}
}
return RedirectToAction("Results", "Exams", new { Controller = "Exams", Action = "Results", id = done.id });
}
}
I first save the new string that came from the radio button value into the result record, then i call it back in the if condition to check it's value
The problem here is i get a
Object reference not set to an instance of an object.
when i post the test, it means that the list is empty, so i need to know what makes the radio buttons not working,
Thanks.
If you want to bind a List of object in Mvc, you should name the controller like "ModelName[indx].PropertyName". In your case it should be "singleQuee[0].Question".
Code Sample
var Indx = 0;
foreach (var item in Model.TheQuestions)
{
.....
var radioName = $"singleQuee[{Indx}].Question";
<div class="col-lg-7" style="text-align:right;margin-right:10px;">
<div class="row">
<input type="radio" name="#radioName" value="1" />
<h5 style="padding-top:3px;padding-right:8px;">#item.RightAnswer</h5>
</div>
</div>
.....
}
Action Method

Additional validation message displayed on mvc app

I have a simple mvc web app, that is searching transactions in the DB using a specified search parameter(named RA number), now I decided to add jquery block ui on my app and I then realized the block fires even when an empty string("" single or double space bar in textbox) is entered.
I have data annotation in my RA view model, I then added an AllowEmptryStrings = false attribute on my view model property, see code below
public class SearchByRAViewModel
{
[Required(ErrorMessage = "Please enter an RA number",AllowEmptyStrings = false)]
public string RANumber { get; set; }
}
Here is my action method from my controller code
public ActionResult SearchTollTransactions(SearchByRAViewModel raViewModel)
{
List<TollScrapingTransactionScreen> tollScrapingList = new List<TollScrapingTransactionScreen>();
if (ModelState.IsValid)
{
tollScrapingList = GetTransactionByRA(raViewModel.RANumber);
ViewBag.RANumber = raViewModel.RANumber;
return View(tollScrapingList);
}
else
{
return View("Index");
}
}
My only problem now is, there seems to be an extra validation message displayed on the search page(index) if there is an empty string in the search text box, see screenshot
Here is my block ui section, in case someone wonders where it is
$('#btnSearch').click(function () {
// there should be at least a value for ra before firing the blockui
var raValue = $('#raNumber').val();
if (!raValue || raValue.trim() === '') {
return;
}
else {
$.blockUI();
}
});
This is the part of my view, which is inside the normal #using(Html.BegingForm)
<div class="form-horizontal">
<div class="form-group">
<div class="panel panel-default">
<div class="panel-heading">Search by RA number</div>
<div class="panel-body">
<div class="col-sm-12">
<table class="table form-table">
<tr>
<th class="col-sm-2">#Html.DisplayNameFor(model => model.RANumber)</th>
<td class="col-sm-10">
#Html.TextBoxFor(model => model.RANumber, new { #class = "form-control", #tabindex = 1, #id = "raNumber" })
#Html.ValidationMessageFor(model => model.RANumber)
</td>
</tr>
</table>
</div>
<div class="btn-toolbar col-sm-12">
<input type="submit" value="Search Transaction" class="btn pull-right" tabindex="2" id="btnSearch" />
</div>
</div>
</div>
</div>
</div>

Ajax BeginForm POST using MVC and Razor

I keep getting a 404 and searching all over SO and cannot target the issue here. The form is the result of a render action and appears on the home page (home controller). However, I want it to post a different controller action and it keeps giving me a 404. I have included all the correct script for unobtrusive javascript as well as the necessary web.config settings and I'm unable to come across a similar problem from my research.
This is the partial with the form that is being rendered:
#model AFS.Models.SearchLocationModel
<div class="site-search-module">
<div class="site-search-module-inside">
#using (Ajax.BeginForm("SearchCare", "LevelOfCare", null, new AjaxOptions { HttpMethod = "POST", InsertionMode = InsertionMode.Replace, UpdateTargetId = "searchDiv" }, new { #class = "search-form", enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<div class="row">
<div class="col-md-12">
<h5>Select a category</h5>
#Html.DropDownListFor(x => x.Level, Model.LevelSelectList, new { #class = "form-control input-lg selectpicker" })
</div>
<div class="col-md-12">
<h5>Enter location</h5>
<input type="text" id="Location" name="Location" class="form-control input-lg selectpicker" placeholder="City, State OR Zip Code" required />
</div>
<div class="col-md-12"> <button type="submit" class="btn btn-primary btn-block btn-lg search"><i class="fa fa-search"></i> Search</button> </div>
</div>
}
</div>
The controller action is:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult SearchCare(SearchLocationModel model)
{
if (ModelState.IsValid)
{
SearchLocationModel geocodeModel = Geocode(new SearchLocationModel() { Level = model.Level, Location = model.Location });
if (geocodeModel.Status == "OK")
{
Session["level"] = model.Level;
return RedirectToRoute("LevelCity", new { level = model.Level, state = geocodeModel.State, city = geocodeModel.City, latitude = geocodeModel.Latitude, longitude = geocodeModel.Longitude });
}
else
{
ModelState.AddModelError(string.Empty, "Please enter City, State OR Zip Code.");
return RedirectToAction("SearchWidget", "Home");
}
}
else
{
return RedirectToAction("SearchError");
}
}

Html.ValidationMessageFor() not rendering <span> element

I've been using Html.ValidationMessageFor for quite a long time in MVC 3. All of a sudden this extension is no longer working for me, but only in a particular view. The extension is used in a <form> tag and the page has the jquery.validate.min.js and jquery.validate.unobtrusive.min.js attached (among others). I've checked other pages of the site, and those views use the same call and the <span> element is generated.
Here is the markup I'm using:
<form id="assistanceRequestDiv" class="form-group js-more-assistance js-hidden">
<p>#Translation.TextByDomain("Assistance", "need-further-assistance-contact-customer-support")</p>
<div class="content-block left-text-children">
<div class="content-block__quarter-column">
#Html.LabelFor(x => x.AssistanceRequestFirstName)
#Html.ValidationMessageFor(x => x.AssistanceRequestFirstName)
#Html.TextBoxFor(x => x.AssistanceRequestFirstName, new {#class = "form-control", required = "required"})
</div>
<div class="content-block__quarter-column">
#Html.LabelFor(x => x.AssistanceRequestLastName)
#Html.ValidationMessageFor(x => x.AssistanceRequestLastName)
#Html.TextBoxFor(x => x.AssistanceRequestLastName, new {#class = "form-control", required = "required"})
</div>
<div class="content-block__quarter-column">
#Html.LabelFor(x => x.AssistanceRequestPhoneNumber)
#Html.ValidationMessageFor(x => x.AssistanceRequestPhoneNumber)
#Html.TextBoxFor(x => x.AssistanceRequestPhoneNumber, new {#class = "form-control"})
</div>
<div class="content-block__quarter-column set-vertical-align-bottom">
<button id="btnSubmitAssistanceRequest" class="btn btn--primary">#Translation.Text("submit")</button>
</div>
</div>
</form>
Data Annotations
[RequiredLocalized, DisplayNameLocalized("first-name")]
public string AssistanceRequestFirstName { get; set; }
[RequiredLocalized, DisplayNameLocalized("last-name")]
public string AssistanceRequestLastName { get; set; }
[RequiredLocalized, DisplayNameLocalized("phone-required")]
[RegularExpressionLocalized(#"(?:(?:\+?1\s*(?:[.-]\s*)?)?(?:(\s*([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9]‌​)\s*)|([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9]))\s*(?:[.-]\s*)?)([2-9]1[02-9]‌​|[2-9][02-9]1|[2-9][02-9]{2})\s*(?:[.-]\s*)?([0-9]{4})", "please-enter-a-valid-10-digit-phone-number", "Assistance")]
public string AssistanceRequestPhoneNumber { get; set; }
RequiredLocalized - Required attribute that returns a custom message. Works in other places of the site.
DisplayNameLocalized - DisplayName attribute with custom message. Works in other places.
Etc
This form is hidden by default and shown when the user clicks a certain button. Here are the scripts that are attached to the page:
<script src="/Scripts/jquery-1.8.3.min.js"></script>
<script src="/Scripts/jquery-ui-1.10.4.custom.min.js"></script>
<script src="/Scripts/jquery.unobtrusive-ajax.min.js"></script>
<script src="/Scripts/jquery.validate.min.js"></script>
<script src="/Scripts/jquery.validate.unobtrusive.min.js"></script>
<script src="/Scripts/jquery.validate.extensions.js"></script>
<script src="/Scripts/Shared/jQueryGlobalSettings.js"></script>
Using Javascript to return false if there are errors on the page in this format:
$('#btnSubmitAssistanceRequest').click(function (e) {
var $answer = $('.js-title');
var $reqFirstName = $('#AssistanceRequestFirstName');
var $reqLastName = $('#AssistanceRequestLastName');
var $reqPhoneNumber = $('#AssistanceRequestPhoneNumber');
// Check for empty fields
if ($reqFirstName.val().trim() == "") {
showErrorMessage($reqFirstName, 'First Name is required.');
} else {
clearErrorMessage($reqFirstName);
}
if ($reqLastName.val().trim() == "") {
showErrorMessage($reqLastName, 'Last Name is required.');
} else {
clearErrorMessage($reqLastName);
}
if ($reqPhoneNumber.val().trim() == "") {
showErrorMessage($reqPhoneNumber, 'Phone Number is required.');
} else {
clearErrorMessage($reqPhoneNumber);
}
// check if validation errors were thrown
if ($(".field-validation-error").length) return false;
$.post('/api/[obfuscated]/[obfuscated]', { answer: $answer.text(), firstName: $reqFirstName.val(), lastName: $reqLastName.val(), phoneNumber: $reqPhoneNumber.val() }, function (data) {
if (data.success) {
$('.request-assistance-success').css('display', 'inline');
$(".feedback-container").slideUp(400);
} else {
$('.request-assistance-failure').css('display', 'inline');
$(".feedback-container").slideUp(400);
}
});
e.preventDefault();
return true;
});
Replace
<button id="btnSubmitAssistanceRequest" class="btn btn--primary">#Translation.Text("submit")</button>
with
<input type="submit" id="btnSubmitAssistanceRequest" class="btn btn--primary" Text="#Translation.Text("submit")"/>
Submit form invokes the unobtrusive validations.

Action in controller received empty model from Html.BeginForm - ASP.NET MVC

I have stucked for 3 days. There is nowhere I had been looking for this problem. When I submit the form, Controller action method doesnt get the model.
My base view Login.cshtml
#{
string durum = ViewBag.Style;
switch (durum)
{
case "Login":
Html.RenderAction("_Login", "Dashboard");
break;
case "LostPassword":
Html.RenderAction("_LostPassword", "Dashboard");
break;
case "RegisterForm":
Html.RenderAction("_RegisterForm", "Dashboard");
break;
default:
Html.RenderAction("_Login", "Dashboard");
break;
}
}
One of my partial view _LostPassword.cshtml
#model HaberSitesi._Entities.Kullanici
#using (Html.BeginForm("LostPassword", "Dashboard", FormMethod.Post, new { #class = "forget-form", #style = "display:block" }))
{
if (TempData["ForgotPassword"] != null)
{
<div class="alert alert-warning ">
<button class="close" data-close="alert"></button>
<span>#TempData["ForgotPassword"]</span>
</div>
}
<h3>Şifrenizi mi unuttunuz ?</h3>
<p> Şifrenizi almak için lütfen E-Posta adresinizi giriniz. </p>
<div class="form-group">
<div class="input-icon">
<i class="fa fa-envelope"></i>
#Html.TextBoxFor(x => x.EPosta, new { #class = "form-control placeholder-no-fix", #type = "email", #autocomplete = "off", #placeholder = "Eposta", Name = "email" })
#Html.ValidationMessageFor(x => x.EPosta)
</div>
</div>
<div class="form-actions">
#Html.ActionLink("Geri Dön", "Login", "Dashboard", new { }, new { #type = "button", #id = "back-btn", #class = "btn grey-salsa btn-outline" })
<button type="submit" class="btn green pull-right"> Gönder </button>
</div>
}
And the action in controller DashboardController.cs
public ActionResult LostPassword()
{
VeriTransfer();
return View("Login");
}
[HttpPost]
public ActionResult LostPassword(Kullanici kullanici)
{
string kullaniciEposta = kullanici.EPosta;
Kullanici user = _kullaniciBll.Get(kullaniciEposta);
if (user != null)
{
TempData["ForgotPassword"] = "Şifreniz e-posta adresinize gönderildi.";
}
else
{
TempData["ForgotPassword"] = "Kayıtlarımızda e-posta adresiniz bulunamadı";
}
VeriTransfer();
return View("Login");
}
When I click submit button, I cant get any data (Kullanici kullanici) in controller. Every property comes null or default data value from model.
Note: Maybe my codes could have some other mistakes which are irrelevant with my problem. I just wonder why I get empty model. Thanks at least you have read my problem.
Your property is called EPosta but you changed the name of it to Name="email".So when the POST action happens it sends a property called email to the controller action and your Kullanici object expects a property called EPosta
Both of these will fix your problem:
Change Name="email" to Name="EPosta" or
Remove Name="email" completely
But like Stephen said it's better to remove it completely,because if you rename your property to EPosta2 in future and forget to change the name to Name="EPosta2" your POST will no longer work

Resources