Not getting a callback with Ajax.BeginForm asp.net mvc - javascript

I am unable to hit the server code using Ajax.BeginForm()
Here is part of my View where I used the Ajax Helper method
#model Ride.MMReports.ViewModels.ManualRecViewModel
.....
var options = new AjaxOptions
{
OnBegin = "OnBeginMethod",
OnFailure = "OnFailureMethod",
OnSuccess = "OnSuccessMethod",
OnComplete = "OnCompleteMethod",
HttpMethod = "Post"
};
using (Ajax.BeginForm("Index", "ManRecReport", options))
{
<button type="submit"
name="action"
value="Export to excel"
id="export-excel"
class="btn btn-primary"
Export to excel
</button>
}
#section scripts
{
#Scripts.Render("~/bundles/report")
#Scripts.Render("~/bundles/jqueryval")
}
My bundle include jquery.unobtrusive-ajax.js and also reports.js where I have all the event methods
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
"~/Scripts/jquery.unobtrusive*",
"~/Scripts/jquery.validate*"));
reports.js bellow. The browser is able to show the alert but last method it calls is OnFailureMethod
var isError = false;
function OnBeginMethod() {
alert("OnBeginMethod");
}
function OnFailureMethod(error) {
isError = true;
alert("OnFailure");
}
function OnSuccessMethod(data) {
alert("OnSuccess");
}
function OnCompleteMethod(data, status) {
if (!isError) {
alert("OnCompleteMethod");
}
}
The problem here is when I click the button, jquery-3.1.1.js is failing
http://localhost:31111/[object%20HTMLButtonElement] 404 (Not Found)
failing at this line
xhr.send( options.hasContent && options.data || null );
My Controller method looks like this:
[HttpPost, ValidateAntiForgeryToken]
public ActionResult Index(ManualRecViewModel vm)
{
....
}
Any thoughts what I am doing wrong?

Related

Method does not return view after Ajax

I have a GetPerson method which returns a People view.
Public ActionResult GetPerson()
{
//code shortened for brevity
return View(people);
}
and here's my view
#model ModelLayer.Models.NotificationModel
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
#addTagHelper *, Kendo.Mvc
#using Kendo.Mvc.UI
#{ ViewData["Title"] = "Index"; }
<div>
//code shortened for brevity
</div>
$("#customSaveButton").on("click", function () {
var model = JSON.parse('#Html.Raw(Json.Serialize(Model?.uploadModels))');
$.ajax({
method: "POST",
url: '#Url.Action("SaveFile", "Upload")',
data: {
model: model,
saveType: saveType
}
})
});
I get the model from the People view and send it to another method SavePeople via an ajax call but my SavePeople method does not want to return a view because of the ajax call and here's my method
public ActionResult SavePeople(List<People> model)
{
//code shortened for brevity
ViewBag.Message = String.Format(cmdMessage);
return View(tModel);
}
What are my options here? Will using a partial view resolve this? Or should I go with something else?

Ajax.beginForms MVC Partial View

I am using an ajax.beginform to create a partial view within another view.
I the user enters a correct sn everything works fine.
But if the user enters an invalid number, I want to redirect to the index view.
Now the index page is submitted as a partial view in itself.
How can I avoid that.
Here is a part of my view and 2 simplified actionresults.
#using (Ajax.BeginForm("MachineInfo", "QrCreate", new AjaxOptions() {
HttpMethod = "POST", UpdateTargetId = "form-content", InsertionMode =
InsertionMode.ReplaceWith }))
{
#Html.AntiForgeryToken()
<input type="text" id="sn" name="sn" class="inputsn"
placeholder="Enter your serial number here..." />
<input type="submit" value="Search" class="search btn btn-success btn-lg" />
}
</div>
</div>
<div id="form-content"></div>
my Controller
public ActionResult Index(bool? isValidMachine = null)
{
ViewBag.invalidSerialNumber = isValidMachine;
return View();
}
[HttpPost]
public ActionResult MachineInfo(string sn)
{
if(string.IsNullOrEmpty(sn))
RedirectToAction("Index", new { isValidMachine = false });
QrCreateViewModel qrCreateVM;
using (var machineService = new MachineApiService())
{
var machine = machineService.GetMachineFromSerialNumber(sn);
if (machine == null)
return RedirectToAction("Index", new { isValidMachine = false });
else
qrCreateVM = new QrCreateViewModel(machine, GetBasePath());
}
if (qrCreateVM.IsValid())
{
qrCreateVM.Viewurl = qrCreateVM.QrCreateUrlOrDefaultNull();
return PartialView(qrCreateVM);
}
else
return RedirectToAction("Index", new { isValidMachine = false });
}
Ajax calls do not redirect (the purpose of making them is to stay on the same page).
In your controller method, replace the instances of return RedirectToAction(...) to return a HttpStatusCodeResult indicating an error, which you can then handle in the OnFailure option to redirect to the Index() method.
For example
[HttpPost]
public ActionResult MachineInfo(string sn)
{
if (string.IsNullOrEmpty(sn))
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest, "Bad Request");
}
....
Then in the Ajax.BeginForm()
#using (Ajax.BeginForm("MachineInfo", "QrCreate", new AjaxOptions() {
HttpMethod = "POST",
UpdateTargetId = "form-content",
InsertionMode = InsertionMode.ReplaceWith,
OnFailure = "redirect"
}))
{
....
and add the following script to redirect
function redirect(ajaxContext) {
location.href = '#Url.Action("Index")';
}

Uploading file and posting text input values in one click?

I'm trying build an Asp.net web api for posting files. I found the following example in
https://code.msdn.microsoft.com/AngularJS-with-Web-API-22f62a6e
The Web API method is:
[RoutePrefix("api/photo")]
public class PhotoController : ApiController
{
private IPhotoManager photoManager;
public PhotoController()
: this(new LocalPhotoManager(HttpRuntime.AppDomainAppPath + #"\Album"))
{
}
public PhotoController(IPhotoManager photoManager)
{
this.photoManager = photoManager;
}
// GET: api/Photo
public async Task<IHttpActionResult> Get()
{
var results = await photoManager.Get();
return Ok(new { photos = results });
}
// POST: api/Photo
public async Task<IHttpActionResult> Post()
{
// Check if the request contains multipart/form-data.
if(!Request.Content.IsMimeMultipartContent("form-data"))
{
return BadRequest("Unsupported media type");
}
try
{
var photos = await photoManager.Add(Request);
return Ok(new { Message = "Photos uploaded ok", Photos = photos });
}
catch (Exception ex)
{
return BadRequest(ex.GetBaseException().Message);
}
}
And the file uploader html code: (I added a text input <input type="text" id="test" value="testit" /> for test.
<form name="newPhotosForm" role="form" enctype="multipart/form-data" ng-disabled="appStatus.busy || photoManagerStatus.uploading">
<div class="form-group" ng-hide="hasFiles">
<label for="newPhotos">select and upload new photos</label>
<input type="file" id="newPhotos" class="uploadFile" accept="image/*" eg-files="photos" has-files="hasFiles" multiple>
<input type="text" id="test" value="testit" /> <!--- Added a text input for test -->
</div>
<div class="form-group" ng-show="hasFiles && !photoManagerStatus.uploading">
<ul class="list-inline">
<li><strong>files:</strong></li>
<li ng-repeat="photo in photos"> {{photo.name}}</li>
</ul>
<input class="btn btn-primary" type="button" eg-upload="upload(photos)" value="upload">
<input class="btn btn-warning" type="reset" value="cancel" />
</div>
<div class="form-group" ng-show="photoManagerStatus.uploading">
<p class="help-block">uploading</p>
</div>
</form>
The JS upload function:
function upload(photos)
{
service.status.uploading = true;
appInfo.setInfo({ busy: true, message: "uploading photos" });
var formData = new FormData();
angular.forEach(photos, function (photo) {
formData.append(photo.name, photo);
});
return photoManagerClient.save(formData)
.$promise
.then(function (result) {
if (result && result.photos) {
result.photos.forEach(function (photo) {
if (!photoExists(photo.name)) {
service.photos.push(photo);
}
});
}
appInfo.setInfo({message: "photos uploaded successfully"});
return result.$promise;
},
function (result) {
appInfo.setInfo({message: "something went wrong: " + result.data.message});
return $q.reject(result);
})
['finally'](
function () {
appInfo.setInfo({ busy: false });
service.status.uploading = false;
});
}
However, it seems the value of the added input test cannot be passed to the Web API code?
You need to add custom DTO/POCO class, set the values and then pass it as parameter to your post method. Since file is not a simple type default MediaTypeFormatter of webAPI won't work so you need to build your custom MediaTypeFormatter.
Sample POCO class
Public Class Attachment
{
public string Input {get;set;}
public byte[] Content{get;set;}
}
Custom Media formatter as below
public class CustomFormatter : MediaTypeFormatter
{
/// <summary>
///
/// </summary>
public CustomFormatter()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue("multipart/form-data"));
}
public override bool CanReadType(Type type)
{
return type == typeof(Attachment);
}
public override bool CanWriteType(Type type)
{
return false;
}
public async override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
{
var provider = await content.ReadAsMultipartAsync();
var modelContent = provider.Contents
.FirstOrDefault(c => c.Headers.ContentType.MediaType == "application/json");
var attachment = await modelContent.ReadAsAsync<Attachment>();
var fileContents = provider.Contents
.Where(c => c.Headers.ContentType.MediaType == "image/jpeg").FirstOrDefault(); // or whatever is the type of file to upload
attachment.Content = await fileContents.ReadAsByteArrayAsync();
return attachment;
}
}
Register the custom media formatter:
private void ConfigureWebApi(HttpConfiguration config)
{
//other code here
config.Formatters.Add(new CustomFormatter());
}
Pass the POCO to your Web-API Controller
public async Task<IHttpActionResult> Post(Attachment attachment)
{
I haven't tested this in Visual Studio, but this is the approach you need to follow
More information here:
http://www.asp.net/web-api/overview/formats-and-model-binding/media-formatters
And a sample here
http://blog.marcinbudny.com/2014/02/sending-binary-data-along-with-rest-api.html#.V5MDDzV7qYg

Controller function call twice in asp.net mvc

When I click the save button to save my controller function is firing twice. What is the problem in my code I don't know. Please help me.
Here is my button click to call ajax and save values.
<button id="btnSave" type="submit" title="Save" class="btn btn-success" onclick="getPage('#(Url.Action("Save", "Carriers"))')">
<span class="glyphicon glyphicon-floppy-disk"></span>Save
</button>
Here is my ajax
$.ajax({
type: "POST",
url: page,
data: $("#frmEdit").serialize(),
xhrFields: {
withCredentials: true
},
success: function (html) {
$('#CarrierList').empty();
$('#CarrierList').append($.parseHTML(html));
},
error: function () {
var error = "Error occured during loading Carrier items...";
$('#errorMessage').empty();
$('#errorMessage').append(error);
$('#errorModal').modal('show');
},
complete: function () {
$('#loaderImg').modal('hide');
}
});
}
Here is my controller method
public override ActionResult Save(CarrierDTO carrierDTO)
{
string[] ErrorMessageArray = new string[4];
int errorIndex = 0;
if (ModelState.IsValid)
{
MessageCollection messages = new MessageCollection();
carrierDTO.Save(ref messages);
if (messages.IsErrorOccured() || messages.IsExceptionOccured())
{
ModelState.AddModelError("", messages[0].Text);
return View("Edit", carrierDTO);
}
return View("Edit", carrierDTO);
}
You need to add 'preventDefault()'.
If the prevent default method is called, the default action of the event will not be
triggered.
In your case, the prevent default will stop submitting the form(the default action of the submit button), and use the ajax snippet to do so instead.
JQ:
$(function(e){
e.preventDefault();
$.ajax({
type: "POST",
url: page,
data: $("#frmEdit").serialize(),
xhrFields: {
withCredentials: true
},
success: function (html) {
$('#CarrierList').empty();
$('#CarrierList').append($.parseHTML(html));
},
error: function () {
var error = "Error occured during loading Carrier items...";
$('#errorMessage').empty();
$('#errorMessage').append(error);
$('#errorModal').modal('show');
},
complete: function () {
$('#loaderImg').modal('hide');
}
});
}
});
Two solutions
Use type="button" in your button control
<button id="btnSave" type="button" title="Save" class="btn btn-success" onclick="getPage('#(Url.Action("Save", "Carriers"))')">
<span class="glyphicon glyphicon-floppy-disk"></span>Save
</button>
or remove onclick="getPage('#(Url.Action("Save", "Carriers"))'), because the submit button take a post action in default .
<button id="btnSave" type="submit" title="Save" class="btn btn-success" ">
<span class="glyphicon glyphicon-floppy-disk"></span>Save
</button>
In you App_Start folder open the BudleConfig.cs file and do few changes:
First take a look to this line (this is an original VS generated content).
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
"~/Scripts/jquery.validate*"));
Note that '...validate*' pattern will load five scripts from your Scripts folder (this is true for my case):
~/Scripts/jquery.validate-vsdoc.js
~/Scripts/jquery.validate.js
~/Scripts/jquery.validate.min.js
~/Scripts/jquery.validate.unobtrusive.js
~/Scripts/jquery.validate.unobtrusive.min.js
As you can see, you are loading the unobtrusive.js twice (jquery.validate.unobtrusive.js and jquery.validate.unobtrusive.min.js). So, make your own code something like this to exclude, say full version js:
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
"~/Scripts/jquery.validate-vsdoc.js"));
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
"~/Scripts/jquery.validate.min.js"));
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
"~/Scripts/jquery.validate.unobtrusive.min.js"));
or make some mechanism to load full or min versions per your desire.

Unable to overload submit method of Ajax.BeginForm

I'm trying to submit an ajax form from my razor view, and I want the controller to return a JSON object. When I use ("#form0").submit(alert("hi");); the data goes to the controller and I get an alert. However, when I use ("#form0").submit(function(){alert("hi");}); the data does not get passed, and I do not get an alert. I get the feeling that this is something minor with my syntax that I'm missing. Here's the relevant code:
jquery:
$(function () {
//setting up the schedule modal dialoag.
$("#schedModal").dialog({
buttons: {
Submit:
function () {
$("#form0").ajaxSubmit(function () {
//this is where I want to put the magic, but I need the alert to fire first.
alert("hi");
return false;
});
},
Cancel:
function () {
$(this).dialog("close");
}
},
autoOpen: false,
minHeight: 350,
modal: true,
resizable: false
});
the targeted view:
#model FSDS.DataModels.Schedule
#using (Ajax.BeginForm("scheduleNew", null, new AjaxOptions { UpdateTargetId = "partial" }, new {}))
{
#Html.ValidationSummary(true)
<div class="editor-label">
#Html.LabelFor(model => model.ScheduleName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.ScheduleName)
#Html.ValidationMessageFor(model => model.ScheduleName)
</div>
#* tons of other labels and editor fields go in here, omitted for brevity. *#
}
The controller, if that matters:
[HttpPost]
public ActionResult scheduleNew(Schedule schedule)
{
if (Request.HttpMethod == "POST")
{
FSDSDBEntities context = new FSDSDBEntities();
if (ModelState.IsValid)
{
context.Schedules.AddObject(schedule);
context.SaveChanges();
}
return Json(schedule);
}
else
{
return PartialView();
}
}
Simply use $('#form0').submit();:
Submit: function () {
$('#form0').submit();
}
Then define an OnSuccess handler in your AjaxForm that will be invoked when the AJAX request succeeds:
#using (Ajax.BeginForm("scheduleNew", null, new AjaxOptions { OnSuccess = "success", UpdateTargetId = "partial" }, new {}))
and finally success javascript handler:
function success(data) {
// the form was successfully submitted using an AJAX call.
// here you could test whether the data parameter
// represents a JSON object or a partial view
if (data.ScheduleName) {
// the controller action returned the schedule JSON object
// => act accordingly
} else {
// the controller action returned a partial view
// => act accordingly
}
}

Categories