I have the following AjaxOptions object:
AjaxOptions ajaxOpts = new AjaxOptions
{
HttpMethod = "Get",
InsertionMode = InsertionMode.Replace
};
In the view I have this form:
#using (Ajax.BeginForm("GetPeopleData", ajaxOpts))
{
<div>
<button type="submit">Submit</button>
</div>
}
This results in the following HTML:
<form action="/People/GetPeopleData" data-ajax="true" data-ajax-method="Get" id="form0" method="post">
<div>
<button type="submit">Submit</button>
</div>
</form>
When I submit the form I can see that a GET request is sent.
Why does the HTML have data-ajax-method="Get" and method="post"?
What is the purpose of the method="post"?
The #Ajax.BeginForm() helper utilize jQuery unobtrusive AJAX library. If you examine the helper return type, it returns System.Web.Mvc.Html.MvcForm, the same return type as #Html.BeginForm() which creates <form> tag:
public static MvcForm BeginForm(
this AjaxHelper ajaxHelper,
AjaxOptions ajaxOptions
)
Since all of its overloads don't have parameter which specify HTTP request in System.Web.Mvc.FormMethod enumeration, it uses default POST request like #Html.BeginForm() has, hence it also writes method="post" for default form method if unobstrusive AJAX script is disabled in client side.
The purpose of data-ajax-method attribute is to override default submit request behavior when unobtrusive AJAX enabled because its value set by AjaxOptions.HttpMethod property, and checked by asyncRequest() method inside unobtrusive AJAX library (see complete version of the script here):
function asyncRequest(element, options) {
var confirm, loading, method, duration;
confirm = element.getAttribute("data-ajax-confirm");
if (confirm && !window.confirm(confirm)) {
return;
}
loading = $(element.getAttribute("data-ajax-loading"));
duration = parseInt(element.getAttribute("data-ajax-loading-duration"), 10) || 0;
$.extend(options, {
type: element.getAttribute("data-ajax-method") || undefined, // here AJAX method is checked (GET or POST)
url: element.getAttribute("data-ajax-url") || undefined,
cache: (element.getAttribute("data-ajax-cache") || "").toLowerCase() === "true",
beforeSend: function (xhr) {
var result;
asyncOnBeforeSend(xhr, method);
result = getFunction(element.getAttribute("data-ajax-begin"), ["xhr"]).apply(element, arguments);
if (result !== false) {
loading.show(duration);
}
return result;
},
complete: function () {
loading.hide(duration);
getFunction(element.getAttribute("data-ajax-complete"), ["xhr", "status"]).apply(element, arguments);
},
success: function (data, status, xhr) {
asyncOnSuccess(element, data, xhr.getResponseHeader("Content-Type") || "text/html");
getFunction(element.getAttribute("data-ajax-success"), ["data", "status", "xhr"]).apply(element, arguments);
},
error: function () {
getFunction(element.getAttribute("data-ajax-failure"), ["xhr", "status", "error"]).apply(element, arguments);
}
});
Note: You can see list of attributes which corresponds to each properties of AjaxOptions in this reference.
Related
We currently has this code
#foreach (var market in Model.Markets.Markets)
{
<li>
#using (Html.BeginForm("Set", "Market", new {marketId = market.Value}, FormMethod.Post, null))
{
#Html.AntiForgeryToken()
<button class="btn btn-none market-list__item jsSelectMarket">
<img src="#market.FlagUrl" class="market-selector__market-icon">
<p class="market-selector__market-text">#market.Text</p>
</button>
}
</li>
}
It looks correct, but profiling shows that a lot of CPU time is spent in generating the anti-forgery tokens (basically 20 times per page for this only).
Is there a way to move #Html.AntiForgeryToken() outside of the foreach so it is only called one?
Thanks.
You should go with Ajax.
You can try with following code for generic method of post
function AjaxPostMethod(url, formName, parameters, successCallback) {
$.validator.unobtrusive.parse($(formName));
$(formName).validate();
if ($(formName).valid()) {
var form = $(formName);
var token = $('input[name="__RequestVerificationToken"]', form).val();
$.ajax({
type: 'POST',
url: url,
data:
{
__RequestVerificationToken: token,
model: parameters
},
success: successCallback,
error: function (xhr, textStatus, errorThrown) {
console.log('error');
}
});
}
Everything in one go
I have a newsletter subscription form in a website I'm creating (it's part of the layout) and I'm using Html.Action() in my layout.cshtml file to call my Subscribe() action method. The method has 'two' versions: one for get and other for post requests.
Problem:
the Subscribe() POST action method gets called whenever any other form is submitted - I don't want that: it should only be called when someone clicks on the 'subscribe' button (then there's an ajax call to update the page without reloading)
For instance, in my contacts page, when I submit the form, the Subscribe() post method also gets called
I'm not sure why this happens but I believe it is because there's a post request and hence my Subscribe() POST method gets called automatically instead of the Subscribe() GET one.I tried using Html.Partial for this instead but it doesn't work because I have to pass a model to the partial view through the layout.cshtml file
_layout.cshtml:
// more code
#Html.Action("Subscribe", "Newsletter", new { area = "" })
// mode code
NewsletterController:
public ActionResult Subscribe() {
NewsletterSubscribeVM model = new NewsletterSubscribeVM();
return PartialView("NewsletterSubscription", model);
}
/// always gets called, even when it shouldn't
[HttpPost, ValidateAntiForgeryToken]
public ActionResult Subscribe(NewsletterSubscribeVM subscriber) {
string message;
if (!ModelState.IsValid) {
message = "Ops! Something went wrong.";
return Json(message, JsonRequestBehavior.AllowGet);
}
Newsletter sub = new Newsletter { Email = subscriber.SubscriberEmail };
this._service.Add(sub);
message = "Thank you for subscribing!";
return Json(message, JsonRequestBehavior.AllowGet);
}
}
NewsletterSubscription.chtml
#model Web.ViewModels.NewsletterSubscribeVM
#using (Html.BeginForm("Subscribe", "Newsletter", new { area = "" }, FormMethod.Post, new { id = "newsletter-form", #class = "col-xs-12" })) {
#Html.AntiForgeryToken()
#Html.Label("Subcribe!", new { id = "newsletter-msg", #class = "col-xs-12 no-padding" })
#Html.TextBoxFor(m => m.SubscriberEmail, new { placeholder = "Email", #class = "col-xs-7", id = "newsletter-box" })
<button id="newsletter-btn" class="col-xs-1">
<i class="fa fa-arrow-right absolute-both-centered" aria-hidden="true"></i> </button>
}
Javascript:
$('#newsletter-btn').click(function (e) {
var form = $('#newsletter-form');
e.preventDefault();
// if the user has inserted at least one character and the 'thank you' msg isn't showing yet:
if ($('#newsletter-box').val().length != 0 && $('#subscription-status-msg').length == 0) {
$.ajax({
method: "POST",
url: "/newsletter/subscribe",
data: form.serialize(),
dataType: "json",
success: function(data) {
$('<div id="subscription-status-msg" class="col-xs-12 no-padding">' + data + '</div>').appendTo($("#newsletter-form")).hide().fadeIn(500);
}
})
}
})
Thanks in advance!
I'd appreciate if someone could help.
I want to return different partial views based on user type:
[HttpPost]
public ActionResult Edit(ContractModel model)
{
if(ModelState.IsValid)
{
if (curUser.IsInputer) { .... return View("Contracts");}
else if(curUser.IsAuthorizer) { ..... return View("Details");}
}
return PartialView(model);
}
View:
#{
string updateRegion = "";
if (curUser.IsInputer)
{
updateRegion = "content";
}
else if (curUser.IsAuthorizer)
{
updateRegion = "mainPane";
}
}
<script>
function returnViewOnFailure(result) { //not firing on submission failure
$("#mainPane").html(result);
}
</script>
#using (Ajax.BeginForm("Edit", new AjaxOptions() { UpdateTargetId = updateRegion,
InsertionMode = InsertionMode.Replace,
OnFailure = "returnViewOnFailure" }))
{.........}
The problem is when ModetState.IsValid= false my function returnViewOnFailure is not firing.
I would like UpdateTargetId be "mainPane" if form submission fails (regardless of user type), otherwise it should depend on curUser.
EDIT:
So, as advised I'm using ajax call to sumbit the form:
<script>
var form = $('#contract_form');
form.submit(function (ev) {
$.ajax({
cache: false,
async: true,
type: "POST",
url: form.attr('action'),
data: form.serialize(),
success: function (data) {
$("#content").html(data);
//How to check ModelState.IsValid and show errors?
}
});
ev.preventDefault();
});
Because the validation is done serverside you can set the statuscode of the response (Controller.Response).
If the validation does not succeed set it to 500 (internal server error) so the ajax.onfailure gets called. Put the validationerror logic (replacing divs content) in there. Or maybee you can even change the updatetargetid in the onfailure (by javascript).
I am having trouble with the AntiForgeryToken with ajax. I'm using ASP.NET MVC 3. I tried the solution in jQuery Ajax calls and the Html.AntiForgeryToken(). Using that solution, the token is now being passed:
var data = { ... } // with token, key is '__RequestVerificationToken'
$.ajax({
type: "POST",
data: data,
datatype: "json",
traditional: true,
contentType: "application/json; charset=utf-8",
url: myURL,
success: function (response) {
...
},
error: function (response) {
...
}
});
When I remove the [ValidateAntiForgeryToken] attribute just to see if the data (with the token) is being passed as parameters to the controller, I can see that they are being passed. But for some reason, the A required anti-forgery token was not supplied or was invalid. message still pops up when I put the attribute back.
Any ideas?
EDIT
The antiforgerytoken is being generated inside a form, but I'm not using a submit action to submit it. Instead, I'm just getting the token's value using jquery and then trying to ajax post that.
Here is the form that contains the token, and is located at the top master page:
<form id="__AjaxAntiForgeryForm" action="#" method="post">
#Html.AntiForgeryToken()
</form>
You have incorrectly specified the contentType to application/json.
Here's an example of how this might work.
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(string someValue)
{
return Json(new { someValue = someValue });
}
}
View:
#using (Html.BeginForm(null, null, FormMethod.Post, new { id = "__AjaxAntiForgeryForm" }))
{
#Html.AntiForgeryToken()
}
<div id="myDiv" data-url="#Url.Action("Index", "Home")">
Click me to send an AJAX request to a controller action
decorated with the [ValidateAntiForgeryToken] attribute
</div>
<script type="text/javascript">
$('#myDiv').submit(function () {
var form = $('#__AjaxAntiForgeryForm');
var token = $('input[name="__RequestVerificationToken"]', form).val();
$.ajax({
url: $(this).data('url'),
type: 'POST',
data: {
__RequestVerificationToken: token,
someValue: 'some value'
},
success: function (result) {
alert(result.someValue);
}
});
return false;
});
</script>
Another (less javascriptish) approach, that I did, goes something like this:
First, an Html helper
public static MvcHtmlString AntiForgeryTokenForAjaxPost(this HtmlHelper helper)
{
var antiForgeryInputTag = helper.AntiForgeryToken().ToString();
// Above gets the following: <input name="__RequestVerificationToken" type="hidden" value="PnQE7R0MIBBAzC7SqtVvwrJpGbRvPgzWHo5dSyoSaZoabRjf9pCyzjujYBU_qKDJmwIOiPRDwBV1TNVdXFVgzAvN9_l2yt9-nf4Owif0qIDz7WRAmydVPIm6_pmJAI--wvvFQO7g0VvoFArFtAR2v6Ch1wmXCZ89v0-lNOGZLZc1" />
var removedStart = antiForgeryInputTag.Replace(#"<input name=""__RequestVerificationToken"" type=""hidden"" value=""", "");
var tokenValue = removedStart.Replace(#""" />", "");
if (antiForgeryInputTag == removedStart || removedStart == tokenValue)
throw new InvalidOperationException("Oops! The Html.AntiForgeryToken() method seems to return something I did not expect.");
return new MvcHtmlString(string.Format(#"{0}:""{1}""", "__RequestVerificationToken", tokenValue));
}
that will return a string
__RequestVerificationToken:"P5g2D8vRyE3aBn7qQKfVVVAsQc853s-naENvpUAPZLipuw0pa_ffBf9cINzFgIRPwsf7Ykjt46ttJy5ox5r3mzpqvmgNYdnKc1125jphQV0NnM5nGFtcXXqoY3RpusTH_WcHPzH4S4l1PmB8Uu7ubZBftqFdxCLC5n-xT0fHcAY1"
so we can use it like this
$(function () {
$("#submit-list").click(function () {
$.ajax({
url: '#Url.Action("SortDataSourceLibraries")',
data: { items: $(".sortable").sortable('toArray'), #Html.AntiForgeryTokenForAjaxPost() },
type: 'post',
traditional: true
});
});
});
And it seems to work!
it is so simple! when you use #Html.AntiForgeryToken() in your html code it means that server has signed this page and each request that is sent to server from this particular page has a sign that is prevented to send a fake request by hackers. so for this page to be authenticated by the server you should go through two steps:
1.send a parameter named __RequestVerificationToken and to gets its value use codes below:
<script type="text/javascript">
function gettoken() {
var token = '#Html.AntiForgeryToken()';
token = $(token).val();
return token;
}
</script>
for example take an ajax call
$.ajax({
type: "POST",
url: "/Account/Login",
data: {
__RequestVerificationToken: gettoken(),
uname: uname,
pass: pass
},
dataType: 'json',
contentType: 'application/x-www-form-urlencoded; charset=utf-8',
success: successFu,
});
and step 2 just decorate your action method by [ValidateAntiForgeryToken]
In Asp.Net Core you can request the token directly, as documented:
#inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf
#functions{
public string GetAntiXsrfRequestToken()
{
return Xsrf.GetAndStoreTokens(Context).RequestToken;
}
}
And use it in javascript:
function DoSomething(id) {
$.post("/something/todo/"+id,
{ "__RequestVerificationToken": '#GetAntiXsrfRequestToken()' });
}
You can add the recommended global filter, as documented:
services.AddMvc(options =>
{
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
})
Update
The above solution works in scripts that are part of the .cshtml. If this is not the case then you can't use this directly. My solution was to use a hidden field to store the value first.
My workaround, still using GetAntiXsrfRequestToken:
When there is no form:
<input type="hidden" id="RequestVerificationToken" value="#GetAntiXsrfRequestToken()">
The name attribute can be omitted since I use the id attribute.
Each form includes this token. So instead of adding yet another copy of the same token in a hidden field, you can also search for an existing field by name. Please note: there can be multiple forms inside a document, so name is in that case not unique. Unlike an id attribute that should be unique.
In the script, find by id:
function DoSomething(id) {
$.post("/something/todo/"+id,
{ "__RequestVerificationToken": $('#RequestVerificationToken').val() });
}
An alternative, without having to reference the token, is to submit the form with script.
Sample form:
<form id="my_form" action="/something/todo/create" method="post">
</form>
The token is automatically added to the form as a hidden field:
<form id="my_form" action="/something/todo/create" method="post">
<input name="__RequestVerificationToken" type="hidden" value="Cf..." /></form>
And submit in the script:
function DoSomething() {
$('#my_form').submit();
}
Or using a post method:
function DoSomething() {
var form = $('#my_form');
$.post("/something/todo/create", form.serialize());
}
In Asp.Net MVC when you use #Html.AntiForgeryToken() Razor creates a hidden input field with name __RequestVerificationToken to store tokens. If you want to write an AJAX implementation you have to fetch this token yourself and pass it as a parameter to the server so it can be validated.
Step 1: Get the token
var token = $('input[name="`__RequestVerificationToken`"]').val();
Step 2: Pass the token in the AJAX call
function registerStudent() {
var student = {
"FirstName": $('#fName').val(),
"LastName": $('#lName').val(),
"Email": $('#email').val(),
"Phone": $('#phone').val(),
};
$.ajax({
url: '/Student/RegisterStudent',
type: 'POST',
data: {
__RequestVerificationToken:token,
student: student,
},
dataType: 'JSON',
contentType:'application/x-www-form-urlencoded; charset=utf-8',
success: function (response) {
if (response.result == "Success") {
alert('Student Registered Succesfully!')
}
},
error: function (x,h,r) {
alert('Something went wrong')
}
})
};
Note: The content type should be 'application/x-www-form-urlencoded; charset=utf-8'
I have uploaded the project on Github; you can download and try it.
https://github.com/lambda2016/AjaxValidateAntiForgeryToken
function DeletePersonel(id) {
var data = new FormData();
data.append("__RequestVerificationToken", "#HtmlHelper.GetAntiForgeryToken()");
$.ajax({
type: 'POST',
url: '/Personel/Delete/' + id,
data: data,
cache: false,
processData: false,
contentType: false,
success: function (result) {
}
});
}
public static class HtmlHelper
{
public static string GetAntiForgeryToken()
{
System.Text.RegularExpressions.Match value = System.Text.RegularExpressions.Regex.Match(System.Web.Helpers.AntiForgery.GetHtml().ToString(), "(?:value=\")(.*)(?:\")");
if (value.Success)
{
return value.Groups[1].Value;
}
return "";
}
}
In Account controller:
// POST: /Account/SendVerificationCodeSMS
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public JsonResult SendVerificationCodeSMS(string PhoneNumber)
{
return Json(PhoneNumber);
}
In View:
$.ajax(
{
url: "/Account/SendVerificationCodeSMS",
method: "POST",
contentType: 'application/x-www-form-urlencoded; charset=utf-8',
dataType: "json",
data: {
PhoneNumber: $('[name="PhoneNumber"]').val(),
__RequestVerificationToken: $('[name="__RequestVerificationToken"]').val()
},
success: function (data, textStatus, jqXHR) {
if (textStatus == "success") {
alert(data);
// Do something on page
}
else {
// Do something on page
}
},
error: function (jqXHR, textStatus, errorThrown) {
console.log(textStatus);
console.log(jqXHR.status);
console.log(jqXHR.statusText);
console.log(jqXHR.responseText);
}
});
It is important to set contentType to 'application/x-www-form-urlencoded; charset=utf-8' or just omit contentTypefrom the object ...
I know this is an old question. But I will add my answer anyway, might help someone like me.
If you dont want to process the result from the controller's post action, like calling the LoggOff method of Accounts controller, you could do as the following version of #DarinDimitrov 's answer:
#using (Html.BeginForm("LoggOff", "Accounts", FormMethod.Post, new { id = "__AjaxAntiForgeryForm" }))
{
#Html.AntiForgeryToken()
}
<!-- this could be a button -->
Submit
<script type="text/javascript">
$('#ajaxSubmit').click(function () {
$('#__AjaxAntiForgeryForm').submit();
return false;
});
</script>
For me the solution was to send the token as a header instead of as a data in the ajax call:
$.ajax({
type: "POST",
url: destinationUrl,
data: someData,
headers:{
"RequestVerificationToken": token
},
dataType: "json",
success: function (response) {
successCallback(response);
},
error: function (xhr, status, error) {
// handle failure
}
});
The token won't work if it was supplied by a different controller. E.g. it won't work if the view was returned by the Accounts controller, but you POST to the Clients controller.
I tried a lot of workarrounds and non of them worked for me. The exception was "The required anti-forgery form field "__RequestVerificationToken" .
What helped me out was to switch form .ajax to .post:
$.post(
url,
$(formId).serialize(),
function (data) {
$(formId).html(data);
});
Feel free to use the function below:
function AjaxPostWithAntiForgeryToken(destinationUrl, successCallback) {
var token = $('input[name="__RequestVerificationToken"]').val();
var headers = {};
headers["__RequestVerificationToken"] = token;
$.ajax({
type: "POST",
url: destinationUrl,
data: { __RequestVerificationToken: token }, // Your other data will go here
dataType: "json",
success: function (response) {
successCallback(response);
},
error: function (xhr, status, error) {
// handle failure
}
});
}
Create a method that will responsible to add token
var addAntiForgeryToken = function (data) {
data.__RequestVerificationToken = $("[name='__RequestVerificationToken']").val();
return data;
};
Now use this method while passing data/parameters to Action like below
var Query = $("#Query").val();
$.ajax({
url: '#Url.Action("GetData", "DataCheck")',
type: "POST",
data: addAntiForgeryToken({ Query: Query }),
dataType: 'JSON',
success: function (data) {
if (data.message == "Success") {
$('#itemtable').html(data.List);
return false;
}
},
error: function (xhr) {
$.notify({
message: 'Error',
status: 'danger',
pos: 'bottom-right'
});
}
});
Here my Action have a single parameter of string type
[HttpPost]
[ValidateAntiForgeryToken]
public JsonResult GetData( string Query)
{
#using (Ajax.BeginForm("SendInvitation", "Profile",
new AjaxOptions { HttpMethod = "POST", OnSuccess = "SendInvitationFn" },
new { #class = "form-horizontal", id = "invitation-form" }))
{
#Html.AntiForgeryToken()
<span class="red" id="invitation-result">#Html.ValidationSummary()</span>
<div class="modal-body">
<div class="row-fluid marg-b-15">
<label class="block">
</label>
<input type="text" id="EmailTo" name="EmailTo" placeholder="forExample#gmail.com" value="" />
</div>
</div>
<div class="modal-footer right">
<div class="row-fluid">
<button type="submit" class="btn btn-changepass-new">send</button>
</div>
</div>
}
I am working on a ASP.NET MVC web site which has a form that allows for the upload of files using the multipart/form data enctype option on the form tag like so
<form enctype="multipart/form-data" method="post" action='<%= Url.Action("Post","Entries",new {id=ViewData.Model.MemberDetermination.DeterminationMemberID}) %>'>
How would I write this to do an ASP.NET MVC Ajax form post instead?
It is possible but it's a long way.
Step 1: write your form
ex:
#using (Ajax.BeginForm(YourMethod, YourController, new { id= Model.Id }, new AjaxOptions {//needed options }, new { enctype = "multipart/form-data" }))
{
<input type="file" id="image" name="image" />
<input type="submit" value="Modify" />
}
Step 2: intercept the request and send it to the server
<script type="text/javascript">
$(function() {
$("#form0").submit(function(event) {
var dataString;
event.preventDefault();
var action = $("#form0").attr("action");
if ($("#form0").attr("enctype") == "multipart/form-data") {
//this only works in some browsers.
//purpose? to submit files over ajax. because screw iframes.
//also, we need to call .get(0) on the jQuery element to turn it into a regular DOM element so that FormData can use it.
dataString = new FormData($("#form0").get(0));
contentType = false;
processData = false;
} else {
// regular form, do your own thing if you need it
}
$.ajax({
type: "POST",
url: action,
data: dataString,
dataType: "json", //change to your own, else read my note above on enabling the JsonValueProviderFactory in MVC
contentType: contentType,
processData: processData,
success: function(data) {
//BTW, data is one of the worst names you can make for a variable
//handleSuccessFunctionHERE(data);
},
error: function(jqXHR, textStatus, errorThrown) {
//do your own thing
alert("fail");
}
});
}); //end .submit()
});
</script>
Step 3: Because you make an ajax call you probably want to replace some image or something of multipart/form-data
ex:
handleSuccessFunctionHERE(data)
{
$.ajax({
type: "GET",
url: "/Profile/GetImageModified",
data: {},
dataType: "text",
success: function (MSG) {
$("#imageUploaded").attr("src", "data:image/gif;base64,"+msg);
},
error: function (msg) {
alert(msg);
}
});
}
The MSG variable is an base64 encrypted string. In my case it's the source of the image.
In this way I managed to change a profile picture and after that the picture is immediately updated.
Also make sure you add in Application_Start (global.asax)
ValueProviderFactories.Factories.Add(new JsonValueProviderFactory());
Pretty nice no?
P.S.: This Solution works so don't hesitate to ask more details.
I came across this little hack, which resolves it nicely
window.addEventListener("submit", function (e) {
var form = e.target;
if (form.getAttribute("enctype") === "multipart/form-data") {
if (form.dataset.ajax) {
e.preventDefault();
e.stopImmediatePropagation();
var xhr = new XMLHttpRequest();
xhr.open(form.method, form.action);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
if (form.dataset.ajaxUpdate) {
var updateTarget = document.querySelector(form.dataset.ajaxUpdate);
if (updateTarget) {
updateTarget.innerHTML = xhr.responseText;
}
}
}
};
xhr.send(new FormData(form));
}
}
}, true);
You can use some additional uploaders (e.g. jQuery multiple file uploader) (I prefer this way and I prefer not to use MS Ajax)
Use:
AjaxHelper.BeginForm("Post", "Entries", new {id=ViewData.Model.MemberDetermination.DeterminationMemberID}, new AjaxOptions(){/*some options*/}, new {enctype="multipart/form-data"})
But in second case I'm not sure that it will work.
The jquery forms plugin supports file uploads in this way.
Code which I used and it works !! It's a copy of #James 'Fluffy' Burton solution. I just improvising his answer so that people who is new to MVC will be able to quickly understand the consequences.
Following are my View:
#using (Ajax.BeginForm("FileUploader", null, new AjaxOptions { HttpMethod = "POST", UpdateTargetId = "AjaxUpdatePanel" }, new { enctype = "multipart/form-data", id = "frmUploader" })){
<div id="AjaxUpdatePanel">
<div class="form-group">
<input type="file" id="dataFile" name="upload" />
</div>
<div class="form-group">
<input type="submit" value="Upload" class="btn btn-default" id="btnUpload"/>
</div>
</div>}
<script>
window.addEventListener("submit", function (e) {
var form = e.target;
if (form.getAttribute("enctype") === "multipart/form-data") {
if (form.dataset.ajax) {
e.preventDefault();
e.stopImmediatePropagation();
var xhr = new XMLHttpRequest();
xhr.open(form.method, form.action);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
if (form.dataset.ajaxUpdate) {
var updateTarget = document.querySelector(form.dataset.ajaxUpdate);
if (updateTarget) {
updateTarget.innerHTML = xhr.responseText;
}
}
}
};
xhr.send(new FormData(form));
}
}
}, true);
Following are my controller:
[HttpPost]
public JsonResult FileUploader(HttpPostedFileBase upload)
{
if (ModelState.IsValid)
{
if (upload != null && upload.ContentLength > 0)
{
if (upload.FileName.EndsWith(".csv"))
{
Stream stream = upload.InputStream;
DataTable csvTable = new DataTable();
using (CsvReader csvReader = new CsvReader(new StreamReader(stream), true))
{
csvTable.Load(csvReader);
}
}
else
{
return Json(new { dataerror = true, errormsg = "This file format is not supported" });
}
}
else
{
return Json(new { dataerror = true, errormsg = "Please Upload Your file" });
}
}
return Json(new { result = true });
}
Following is the quick Note of above code:
Through Ajax, I have posted my excel (*.csv) file to Server and read it to an DataTable using a Nuget package (LumenWorksCsvReader).
Hurray! It works. Thanks #James
I actually answered the question myself...
<% using (Ajax.BeginForm("Post", "Entries", new { id = ViewData.Model.MemberDetermination.DeterminationMemberID }, new AjaxOptions { UpdateTargetId = "dc_goal_placeholder" }, new { enctype = "multipart/form-data" }))
For those who still have problems using #Ajax.BeginForm for multipart enctypes / file uploads in MVC
Diagnosis and proposed solution
Running the “Inspect element” tool on a form element generated by the #Ajax.BeginForm helper reveals that the helper, rather inexplicably, overrides the controller parameter specified. This is the case if you implemented a separate controller for your partial postback.
A quick-fix for the problem is to explicitly specify your html action attribute value as /<yourcontrollername>/<youractionname>.
Example
#using (Ajax.BeginForm("", "", new AjaxOptions() { HttpMethod = "POST", UpdateTargetId = "<TargetElementId>", InsertionMode = InsertionMode.Replace }, new { enctype = "multipart/form-data", action = "/<Controller>/<Action>" }))
If you need to use the OnSuccess AjaxOption and/or use Request.IsAjaxRequest() in the controller to check the request type i.e.
#using (Ajax.BeginForm("FileUploader", null, new AjaxOptions { HttpMethod = "POST", UpdateTargetId = "elementToUpdate", OnSuccess = "mySuccessFuntion(returnedData)", OnFailure = "myFailureFuntion(returnedData)"}, new { enctype = "multipart/form-data" }))
Then you can use the following code (I've modified #James 'Fluffy' Burton's answer). This will also convert the response text to JSON object if it can (you can omit this if you want).
<script>
if(typeof window.FormData === 'undefined') {
alert("This browser doesn't support HTML5 file uploads!");
}
window.addEventListener("submit", function (e) {
var form = e.target;
if (form.getAttribute("enctype") === "multipart/form-data") {
if (form.dataset.ajax) {
e.preventDefault();
e.stopImmediatePropagation();
var xhr = new XMLHttpRequest();
xhr.open(form.method, form.action);
xhr.setRequestHeader("x-Requested-With", "XMLHttpRequest"); // this allows 'Request.IsAjaxRequest()' to work in the controller code
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
var returnedData; //this variable needs to be named the same as the parameter in the function call specified for the AjaxOptions.OnSuccess
try {
returnedData = JSON.parse(xhr.responseText); //I also want my returned data to be parsed if it is a JSON object
}catch(e){
returnedData = xhr.responseText;
}
if (form.dataset.ajaxSuccess) {
eval(form.dataset.ajaxSuccess); //converts function text to real function and executes (not very safe though)
}
else if (form.dataset.ajaxFailure) {
eval(form.dataset.ajaxFailure);
}
if (form.dataset.ajaxUpdate) {
var updateTarget = document.querySelector(form.dataset.ajaxUpdate);
if (updateTarget) {
updateTarget.innerHTML = data;
}
}
}
};
xhr.send(new FormData(form));
}
}
}, true);
</script>
N.B. I use the javascript function eval() to convert the string in to a function... if anyone has a better solution please comment.
I also use JQuery JSON.parse() so this isn't a vanilla javascript solution but it isn't required for the script to function so it could be removed.
I mixed Brad Larson answer with Amirhossein Mehrvarzi, because Brad answer wasn't providing any way to handle the response and Amirhossein was causing 2 postbacks.
I just added ($('#formBacklink').valid()) to call model validation before send.
window.addEventListener("submit", function (e) {
if ($('#formBacklink').valid()) {
var form = e.target;
if (form.getAttribute("enctype") === "multipart/form-data") {
if (form.dataset.ajax) {
e.preventDefault();
e.stopImmediatePropagation();
var dataString;
event.preventDefault();
var action = $("#formBacklink").attr("action");
if ($("#formBacklink").attr("enctype") == "multipart/form-data") {
//this only works in some browsers.
//purpose? to submit files over ajax. because screw iframes.
//also, we need to call .get(0) on the jQuery element to turn it into a regular DOM element so that FormData can use it.
dataString = new FormData($("#formBacklink").get(0));
contentType = false;
processData = false;
} else {
// regular form, do your own thing if you need it
}
$.ajax({
type: "POST",
url: action,
data: dataString,
dataType: "json", //change to your own, else read my note above on enabling the JsonValueProviderFactory in MVC
contentType: contentType,
processData: processData,
success: function (data) {
//BTW, data is one of the worst names you can make for a variable
//handleSuccessFunctionHERE(data);
},
error: function (jqXHR, textStatus, errorThrown) {
//do your own thing
}
});
}
}
}
}, true);
Ajax.BegineForm() works with multipart form data and here's the working code example for the same:
View:
#using(Ajax.BeginForm("UploadFile","MyPOC",
new AjaxOptions {
HttpMethod = "POST"
},
new
{
enctype = "multipart/form-data"
}))
{
<input type="file" name="files" id="fileUploaderControl" />
<input type="submit" value="Upload" id="btnFileUpload" />
}
Controller Action Method:
public void UploadFile(IEnumerable<HttpPostedFileBase> files)
{
HttpPostedFileBase file = files.FirstOrDefault(); //Attach a debugger here and check whether you are getting your file on server side or null.
if (file != null && file.ContentLength > 0)
{
//Do other validations before saving the file
//Save File
file.SaveAs(path);
}
}
P.S. Make sure the "name" attribute of the file uploader control and the name of the parameter passed to Action method UploadFile() has to be same (i.e. "files" in this case).
From my little investigation. All the answers above seems to be correct depending on the problem one is having with the Ajax.BeginForm. However, I have just observe that the problem is with the ~/Scripts/jquery.unobtrusive-ajax.min.js javascript library in some case. So in my case I just removed it from the view model and sort of decided to use JQuery Form plugin for my required need along with the HTML Form instead. This has been suggested above.
You can use this code instead of eval
var body = "function(a){ " + form.dataset.ajaxSuccess + "(a) }";
var wrap = s => "{ return " + body + " };"
var func = new Function(wrap(body));
func.call(null).call(null, returnedData);