ASP MVC - Html.Action [post] firing for every post request - asp.net

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!

Related

Sending parameters of selected values to controller

I have html like this:
HTML
<div class="col-md-3 col-sm-12">
<div>
<p>Región</p>
<select id="lstRegion" class="form-control agenda_space" aria-hidden="true"></select>
</div>
<div>
<p>Solicitud</p>
<select id="lstSolicitud" class="form-control agenda_space" aria-hidden="true"> </select>
</div>
<br/>
<div>
Actualizar Filtro
<br/>
</div>
JS:
$("#lstRegion")
.getJSONCatalog({
onSuccess: function (response) {
console.log(response);
},
url: '/Agenda/GetRegion',
valueProperty: "ID",
textProperty: "valor"
});
//Load solicitud dropdown
$("#lstSolicitud")
.getJSONCatalog({
url: '/Agenda/GetSolicitud',
valueProperty: "ID",
textProperty: "solicitud"
});
Controller:
public ActionResult GetRegion()
{
try
{
var listaRegistros = db.CatalogoRegistros.Where(x => x.CatalogosCodigo == "REGI").Select(x => new
{
x.ID
,
valor = x.Valor
});
return Json(listaRegistros, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
throw ex;
}
}
public ActionResult GetSolicitud()
{
try
{
var listasolicitud = db.Solicitudes.Select(x => new { x.ID, solicitud = "Folio: " + x.ID });
return Json(listasolicitud, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
throw ex;
}
}
They work great I get my dropdwon lists very well, but now I want to do a GET action with selected values of each dropdown when my Actualizar Filtro it´s clicked.
But I´m really new in asp.net and I don´t know what I need to do to get selected values and send to controller.
As googling it I found I need to do method into my controller to get values so:
Controller will be:
public ActionResult GetTareas(string lstRegionValue, string lstsolicitudValue)
{
}
But I don´t know how to send them via JS, how can I do that to receive selected parameters into my controller? Regards
UPDATE
I try it using Ajax like:
$.ajax({
type: 'GET',
url: '#Url.Action("Agenda", "GetTareas")',
data: { region: $('#lstRegion option:selected').html(), solicitud: $('#lstSolicitud option:selected').html() }, // pass the value to the id parameter
dataType: 'json',
success: function (data) {
console.log(data);
}});
But how can I trigger that function when event_add is clicked?
To run your updated ajax code on click, add #event_add click event handler and run your code inside it.
$('#event_add').click(function(e){
e.preventDefault(); //suppress default behavior
$.ajax({
type: 'GET',
url: '#Url.Action("Agenda", "GetTareas")', // don't hard code your urls
data: { region: $('#lstRegion option:selected').html(), solicitud:
$('#lstSolicitud option:selected').html() }, // pass the value to the id parameter
dataType: 'json', // your returning a view, not json
success: function (data) {
console.log(data);
}});
});
Hi Try the below updated code:
$('#event_add').click(function(e){
var regionval = $('#lstRegion option:selected').html(),
var solicval = $('#lstSolicitud option:selected').html(),
$.ajax({
type: 'GET',
url: '#Url.Action("Agenda", "GetTareas", new { lstRegionValue = regionval, lstsolicitudValue =solicval})',
});
});
Note : I didnt test the code, but hope that it should work for you
Controller code:
public ActionResult GetTareas(string lstRegionValue, string lstsolicitudValue)
{
}
Hope it helps , thanks

Change UpdateTargetId of Ajax.BeginForm() dynamically based on form submission result

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).

Asp.net MVC Selected Index Changed / Ajax.ActionLink

I have a page in Asp.Net MVC using the Razor syntax. In the page I have a DropDownList, and a DIV section that I would like to replace the content in the DIV depending on the selected value of the combobox using the Ajax.ActionLink functionality
Is there a way to do this?
Thanks
Mark
I would rather use a form than a link as it is more adapted to what you are trying to achieve:
#using (Ajax.BeginForm(
"Change",
"Home",
new AjaxOptions { UpdateTargetId = "result" }
))
{
#Html.FropDownListFor(x => x.Foo, Model.Foos, "-- Select a foo --")
<input type="submit" value="Change" />
}
<div id="result"></div>
And the corresponding controller action:
public ActionResult Change(string foo)
{
string view = ... // select the partial based on the selected value
return PartialView(view);
}
you can use jquery to call your ajax action
function OnDddlChanged() {
$.ajax({
url: "controller/action/"+$("#SelectTagID").val(),
dataType: 'html',
type: 'Get',
success: function (r) {
$('#DivID').html(r);
},
error: function () {
alert('Error');
}
});
}
mvc ajax action will like that
public MvcHtmlString MyAction(string id)
{
if(Request.IsAjaxRequest)
{
//return your html as MvcHtmlString
}
}

ASP.NET MVC Login Modal problems - redirect goes to modal?

I have the login view showing up in a modal dialog (using jquery simplemodal), and when the user fails login - it works fine - the error goes back to the modal. The problem is when the user successfully logs in - the page is supposed to be redirected and the modal disappears, but the modal is now updated with the entire page :(
In order for the modal to look correct - i check to see in the AccountController if the source of the login was Ajax, if so - return a partial view (to display in the modal) if not, return the full view (in case a user tries to access a page from outside of the website)
public ActionResult LogOn()
{
if (Request.IsAjaxRequest())
{
return PartialView("_LogOn");
}
return View();
}
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (MembershipService.ValidateUser(model.UserName, model.Password))
{
FormsService.SignIn(model.UserName, model.RememberMe);
if (!String.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
// If we got this far, something failed, redisplay form
if (Request.IsAjaxRequest())
{
return PartialView("_LogOn");
}
return View();
}
In my Logon UserControl (the one thats in Site.Master) i added a script to logon from javascript when the login link is clicked
<script type="text/javascript">
function openModel() {
$.ajax({
type: "get",
url: '/Account/LogOn',
dataType: "html",
contentType: "application/x-www-form-urlencoded;charset=utf-8",
// async: true,
success: function (result) {
$.modal(result, {
closeHTML: "",
containerId: "login-container",
overlayClose: true
});
}
});
}
</script>
While the partial view (that gets returned on Ajax call) looks like this:
<% using (Ajax.BeginForm("LogOn", "Account", new AjaxOptions{UpdateTargetId = "logonForm"})) { %>
<div id="logonForm">
<%: Html.ValidationSummary(true, "Login was unsuccessful. Please review and try again.") %>
<p>Please enter your username and password.
</p>
<p>
<input id="submit" type="submit" value="Log In" />
</p>
</div>
<% } %>
So the problem is that the AjaxOptions UpdateTargetId is the logonForm - which is what i want when the user fails to login, but when the successfully login..i want it to update the page normally - but i cant differentiate between ajax login and normal login in AccountController.LogOn unless i do this :P
Any ideas? Hope this makes sense - thanks
Its like im screwed either way.
So instead of always returning html back to the success javascript callback I'd recommended returning json wherever you have a return RedirectToAction or return Redirect.
if (Request.IsAjaxRequest())
return Json(new {Url = returnUrl});
else
return Redirect(returnUrl);
Or
if (Request.IsAjaxRequest())
return Json(new {Url = Url.Action("Index", "Home")});
else
return RedirectToAction("Index", "Home");
On the javascript side do something like
success: function (result) {
if(typeof result === 'string'){
$.modal(result, {
closeHTML: "",
containerId: "login-container",
overlayClose: true
});
}
else if (result.Url){
location.href = result.Url;
}
}
Some simple jQuery to place on pages that should "break out" if accidentally displayed in a dialog:
<script type="text/javascript">
$(function () {
if ($('title').length > 1) {
location = '<%: Request.RawUrl %>';
}
});
</script>

How to do a ASP.NET MVC Ajax form post with multipart/form-data?

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);

Resources