My issue is that I have a partial view which holds a form full of drop downs. I am using AJAX to dynamically update drop down content. Everything works fine until I implement the Anti Forgery Tokens. Upon serialising my form in the ajax post, I am picking up both the anti forgery token in my _LoginPartial.cshtml
form and the anti forgery token in the form I am posting data from _config.cshtml
. This is leading to the validation attribute canning the post and stopping ajax from working.
I have tried the following:
Removing all tokens and just placing a single token in the _Layout.cshtml
page and referring to that in the ajax call. This works for Ajax but upon submitting the form I get a token not found error since the token is not in the form.
I then tried posting the form with AJAX too and referring to the single token in the _Layout.cshtml
view. This returned data to ajax and not an entire view as I was hoping.
My questions are:
I am pretty certain there is a simple way to sort this out but I am really just a beginner with all this and I am trying to find my feet. Any help would be appreciated.
_LoginPartial.cshtml
@using Microsoft.AspNet.Identity
@using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id = "logoutForm", @class = "navbar-right" }))
{
if (Request.IsAuthenticated)
{
<ul class="nav navbar-nav navbar-right">
<li>
@Html.ActionLink("Hello " + User.Identity.GetUserName() + "!", "Index", "Manage", routeValues: null, htmlAttributes: new { title = "Manage" })
</li>
@if (User.IsInRole("admin"))
{
<li>@Html.ActionLink("Register", "Register", "Account", routeValues: null, htmlAttributes: new { id = "registerLink" })</li>
}
<li><a href="javascript:document.getElementById('logoutForm').submit()">Log off</a></li>
</ul>
}
else
{
<ul class="nav navbar-nav navbar-right">
@if (User.IsInRole("admin"))
{
<li>@Html.ActionLink("Register", "Register", "Account", routeValues: null, htmlAttributes: new { id = "registerLink" })</li>
}
<li>@Html.ActionLink("Log in", "Login", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" })</li>
</ul>
}
_Layout.cshtml
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@ViewBag.Title - Doogies Website</title>
@Styles.Render("~/Content/css")
@Styles.Render("~/Content/themes/base/css")
@Scripts.Render("~/bundles/modernizr")
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/jqueryui")
@Scripts.Render("~/bundles/jqueryval")
</head>
<body>
@using (Html.BeginForm(null, null, FormMethod.Post, new { id = "_AFT"})) { @Html.AntiForgeryToken()}
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
@Html.ActionLink("Doogies Site", "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li>@Html.ActionLink("Home", "Index", "Home")</li>
<li>@Html.ActionLink("About", "About", "Home")</li>
<li>@Html.ActionLink("Contact", "Contact", "Home")</li>
<li>@Html.ActionLink("RTCG", "RTCG_Configurator", "Rtcg")</li>
</ul>
@Html.Partial("_LoginPartial")
</div>
</div>
</div>
<div class="container body-content" >
@RenderBody()
<hr />
<footer>
<p>© @DateTime.Now.Year - Chris Duguid</p>
</footer>
</div>
@Scripts.Render("~/bundles/bootstrap")
@RenderSection("scripts", required: false)
</body>
</html>
_config.cshtml (partial view where the form resides)
Yes I am aware the form has no submit information. I am at the point where I was trying out the submitting using ajax.
@model HomeWeb.Models.RtcgConfigurationModel
@using (Html.BeginForm( new { enctype = "multipart/form-data" , id = "configform"}))
{
<div class="form-horizontal">
<h3>Configure RTCG</h3>
<hr />
@Html.ValidationSummary(true)
<div class="form-group">
@Html.LabelFor(model => model.RtcgCabinetType, "Cabinet Type", new { @class = "control-label col-md-5" })
<div class="col-md-7">
@Html.DropDownListFor(model => Model.RtcgCabinetType, Model.Cabinets, new { onchange = "changed();" })
@Html.ValidationMessageFor(model => model.RtcgCabinetType)
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.RtcgAdaptorType, "Adaptor Type", new { @class = "control-label col-md-5" })
<div class="col-md-7">
@Html.DropDownListFor(model => Model.RtcgAdaptorType, Model.Adaptors, new { onchange = "changed();" })
@Html.ValidationMessageFor(model => model.RtcgAdaptorType)
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.RtcgQtyAdaptors, "Adaptor Qty.", new { @class = "control-label col-md-5" })
<div class="col-md-7">
@Html.DropDownListFor(model => Model.RtcgQtyAdaptors, Model.AdaptorQtys, new { onchange = "changed();" })
@Html.ValidationMessageFor(model => model.RtcgQtyAdaptors)
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.RtcgTerminationMethod, "Termination", new { @class = "control-label col-md-5" })
<div class="col-md-7">
@Html.DropDownListFor(model => Model.RtcgTerminationMethod, Model.TerminationMethods)
@Html.ValidationMessageFor(model => model.RtcgTerminationMethod)
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.RtcgFaceplateStyle, "Faceplate Style", new { @class = "control-label col-md-5" })
<div class="col-md-7">
@Html.DropDownListFor(model => Model.RtcgFaceplateStyle, Model.FacePlateStyles)
@Html.ValidationMessageFor(model => model.RtcgFaceplateStyle)
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.RtcgScreenPrinting, "Screenprinting", new { @class = "control-label col-md-5" })
<div class="col-md-7">
@Html.DropDownListFor(model => Model.RtcgScreenPrinting, Model.ScreenPrintOptions)
@Html.ValidationMessageFor(model => model.RtcgScreenPrinting)
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.RtcgApplication, "Fibre Type", new { @class = "control-label col-md-5" })
<div class="col-md-7">
@Html.DropDownListFor(model => Model.RtcgApplication, Model.Applications)
@Html.ValidationMessageFor(model => model.RtcgApplication)
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.RtcgVerminProof, "Vermin Proof", new { @class = "control-label col-md-5" })
<div class="col-md-7">
@Html.DropDownListFor(model => Model.RtcgVerminProof, Model.VerminProofing)
@Html.ValidationMessageFor(model => model.RtcgVerminProof)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-5 col-md-10">
<input value="Get Quotation" class="btn btn-primary" style="float: none" onclick="submitajax()" />
</div>
</div>
</div>
}
<script type="text/javascript">
function changed() {
var tokensource = $('#_AFT');
var formData = $('form').serialize();
var token = $('input[name="__RequestVerificationToken"]', tokensource).val();
$.extend(formData, { '__RequestVerificationToken': token });
$.ajax({
url: '/Rtcg/Ajax_DropdownChanged',
type: "POST",
data: formData,
success: function (result) {
$('#config-form').html(result);
},
})
}
function submitajax() {
var tokensource = $('#_AFT');
var formData = $('form').serialize();
var token = $('input[name="__RequestVerificationToken"]', tokensource).val();
$.extend(formData, { '__RequestVerificationToken': token });
$.ajax({
url: '/Rtcg/updateDB',
type: "POST",
data: formData,
success: function (result) {
},
})
}
</script>
RTCG_Configurator ('index' view presented when body rendered)
@model HomeWeb.Models.RtcgConfigurationModel
@{
ViewBag.Title = "RTCG Configurator";
}
<section >
<div class="col-md-6 pv" id="config-form">
@Html.Partial("_config")
</div>
<div class="col-md-6" >
</div>
</section>
<div style="clear:both">
<br /><br />
<p><a href="~/Home/Index" class="btn btn-primary btn-sm">« Back Home </a></p>
</div>
Controller - Ajax_DropdownChanged()
// POST: handle dropdown changes from Ajax
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Ajax_DropdownChanged(RtcgConfigurationModel formData)
{
//handle 'postback' displaying the fields.
if (ModelState.IsValid)
{
RtcgConfigurationModel model = new RtcgConfigurationModel();
using (var db = new ApplicationDbContext())
{
model.Cabinets = DisplayElement.Cabinets(formData);
model.Adaptors = DisplayElement.Adaptors(formData);
model.AdaptorQtys = DisplayElement.AdaptorQuantities(formData);
model.TerminationMethods = DisplayElement.Termination(formData);
model.FacePlateStyles = DisplayElement.Faceplates(formData);
model.ScreenPrintOptions = DisplayElement.ScreenPrinting(formData);
model.Applications = DisplayElement.Application(formData);
model.VerminProofing = DisplayElement.VerminProof(formData);
}
return PartialView("_config", model);
//return View(model);
}
else
{
return PartialView();
}
}
Your use of var formData = $('form').serialize();
will serialize the values of all form controls in all forms in the view, including the antiforgery tokens, which is why you submitting multiple tokens.
Ensure each form has an id
attribute, and then to submit only the values (and the token) in the form with id="configform"
, use
var formData = $('#configform').serialize();
Note also that you do not need
var token = $('input[name="__RequestVerificationToken"]', tokensource).val();
$.extend(formData, { '__RequestVerificationToken': token });
since .serialize()
already includes the name/value pair for the token (assuming @Html.AntiForgeryToken()
is included between the <form>
tags)