C# asp.net mvc custom error handling with JSON - c#

I have a complex type that I return when I perform various operations.And I want to know how I can pass back a custom JSON object to show any errors. Consider the following code
Business Layer
public ResultObject StartJob(string jobName){
...
return new ResultObject{ErrorMessage = "Job cannot be started due to ..."};
}
Controller
[HttpPost]
public ActionResult StartJob(string jobName){
var resultObject = BusinessLayer.StartJob(jobName);
if (resultObject.HasErrors){
return Json(new {success = false, message = resultObject.message}, JsonRequestBehavior.AllowGet);
}
else{
return Json(new {success = true}, JsonRequestBeahvior.AllowGet);
}
}
When I perform the ajax post, despite me returning success = false, the ajax call is still successful and jQuery does not call error() method.
This type of pattern is repeated multiple times.

The "issue" here is that the ajax call IS a success. JQuery will only execute the 'error()' method when the ajax call fails. You should read the value of the success variable on the client side callback and if it is false then call 'error()' i.e.
if (!data.success)
{
error();
}

Its going to call success Event
If you want to trigger error event then throw an custom exception in between.
Keep exception unhandled it will go inside error Scope.
error: function() {
.......
.......
}
Or if you want to handle it inside Ajax success
go for
success: function(data) {
if (!data.success)
{
error();
}

Related

Does AJAX call reload my entire page in Razor Pages?

I'm trying to do an AJAX call from my Razor Pages page.
JavaScript:
$.ajax({
type: 'POST',
url: '?handler=Delete',
data: {
id: $(this).data('id')
},
beforeSend: function (xhr) {
// Required for POST, but have same issue if I removed this
// and use GET
xhr.setRequestHeader('XSRF-TOKEN',
$('input:hidden[name="__RequestVerificationToken"]').val());
},
})
.fail(function (e) {
alert(e.responseText);
})
.always(function () {
// Removed to simplify issue
//location.reload(true);
});
Code behind:
public async System.Threading.Tasks.Task OnPostDeleteAsync(int id)
{
string userId = UserManager.GetUserId(User);
var area = DbContext.Areas.FirstOrDefault(a => a.UserId == userId && a.Id == id);
if (area != null)
{
DbContext.Remove(area);
await DbContext.SaveChangesAsync();
}
}
And, in fact, this works correctly and the item is deleted.
However, the $.ajax.fail() handler is called and the error indicates there was a NullReferenceException.
The exception is raised in my markup (CSHTML file):
#if (Model.ActiveAreas.Count == 0) #***** NullReferenceException here! *****#
{
<div class="alert alert-info">
You don't have any active life areas.
</div>
}
The reason there is this exception is because all of the properties of my page model are null. They are null because my OnGetAsync() method is never called to initialize them!
My question is why is my markup executing as though I'm updating the entire page? I'm doing an AJAX call. I don't want to update the entire page. So I don't know why my OnGetAsync() would ever need to be called.
(Based on your provided code)
I think problem in your a always() callback function or the Cliek event or your return statement.
always():
The callback function passed to the always() function is called whenever the AJAX request finishes, regardless of whether or not the AJAX request succeeds or fails. The three parameters passed to the callback function will be either the same three parameters passed to done() or fail(), depending on whether the AJAX request succeeds or fails.
In response to a successful request, the function's arguments are the same as those of .done(): data, textStatus, and the jqXHR object. For failed requests the arguments are the same as those of .fail(): the jqXHR object, textStatus, and errorThrown.
The deferred.always() method receives the arguments that were used to .resolve() or .reject() the Deferred object, which are often very different. For this reason, it's best to use it only for actions that do not require inspecting the arguments. In all other cases, use explicit .done() or .fail() handlers since the arguments will have well-known orders.
Reference:
http://tutorials.jenkov.com/jquery/ajax.html
https://api.jquery.com/jquery.ajax/
https://api.jquery.com/deferred.always/
OR
Cliek event: I think your button is triggering a page refresh. so use preventDefault() in your click event.
Example:
$('...').on('click', function (event) {
event.preventDefault();//add the prevent default.
//Your code
});
OR
Return Statement: If your OnPostDelete method return Page() the please change it to JsonResult or RedirectToPage.
Example:
public Task<IActionResult> OnPostDelete(int? id)
{
if (id == null)
{
return NotFound();
}
//Your Code
return Page();//Remove it
return new JsonResult ("Customer Deleted Successfully!");// Use some thing like it as u need.
return RedirectToPage("./Index");//if u want to reload page then use it.
}

How do I do a redirect an Ajax Post in a Controller's Action Method?

I have an Ajax POST being sent to one of my Controller. e.g.
Enquiry > Index.
In the Index() Action, I return an ActionResult of JSON.
As I am doing login checks, I do an if statement and if the activeUser's session token is null, I do a
return RedirectToAction("Index","Auth");
However, this is not redirecting properly but instead is returning the result in JSON which the browser complains about.
How do I set a redirect properly such that if the IF statement fails, it will go to the login page but otherwise it will return the JSON results that I require?
return Json(new{data=true})
on the ajax success method
$.ajax({
type: 'POST',
url: 'your url',
data:your data
success: function (data) {
if (data) {
window.location.href="url";
}
else {
window.location.href="url";
}
},
error: function (xhr, textStatus, errorThrown) {
alert(errorThrown);
}
});
return false;
You could do it like this :
Return a JSON result in both the cases, just return a false JSON reponse when your condition is false. In that way, your call will be returned to the AJAX on your view and then you can check the false value there and if matched, you can use javascript redirects to redirect to the appropriate view.
Hope this clears the idea that you want.
In Controller, return true or false in JSON. Then in AJAX success, if true, set window.location to new URL.
window.location = '#Url.Action("Index","Auth")';
An ajax requested action will not let you redirect in this way.
In your error case, you can return an error value of some sort, and handle this case back in the jQuery in the view. This could be redirecting to an error page, displaying an error message etc.

Strange Behaviour with Ajax

I am working on ASP.net MVC 2.0 Application. I am using Ajax form. In that I have a dropdown and a button.
After clicking on button, i wanted to reload the dropdown if the Ajax request is sucessful.
So, i am calling a jquery Ajax function inside the callback function of the Ajax form.
In that jquery Ajax function, I am writing code to get the new data and binding to drop down so that it will be reloaded with new data.
Here, Every thing is working fine for the first time. But, when i click the button for the next time, the Ajax jquery function is called but it is not hitting contoller action method.
Code:
Here, are my jquery functions:
<script type="text/javascript">
function GetData() {
$.getJSON("/Home/GetUsers", null, function (data) {
var selectList = $("#ddlUsers");
selectList.empty();
alert("Inside Get Json method of jquery Ajax");
var defaultoption = $('<option>').text("--Select--").val("");
selectList.append(defaultoption);
$.each(data, function (index, optionData) {
var option = $('<option>').text(optionData.Text).val(optionData.Value);
alert(option);
selectList.append(option);
});
});
}
function Callback() {
GetData();
alert("Sucessfully done");
}
function Failed() {
alert("Sorry, an error occured while processing your request");
}
Methods inside Contoller:
[HttpGet]
public JsonResult GetUsers() ----> Method that is called from GetData() Ajax call
{
var data = GetUsersList();
return Json(data, JsonRequestBehavior.AllowGet);
}
public SelectList GetUsersList()
{
Db Fectch
return data;
}
I am unable to understand why the above indicated method is not called for second time yet the GetData() is called.
Please help..
I think it's the cache, try adding this line before your controller method:
[OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")]
You can also place this line at the top of the controller, if you want it to be applied to all methods.

AJAX error in ASP.NET c#

I am very new to Ajax and ASP.NET MVC. I have a function, that returns back to AJAX and I need to handle the error situation. When everything works fine, then the code is okay. My question is how to handle the error part. Here is what I have:
To return success I have:
var data = new { success = false };
return Json(data, JsonRequestBehavior.AllowGet);
And I need to know what to return when there is an exception or error??
This is my query:
function DoMailPDF() {
$("#submitMail").attr("disabled", true);
var personid = $("#personid").val();
var unitid = $("#unitid").val();
var url = "#(Url.Action("SendEmail", "Report"))";
$.ajax({
url: url,
data: { person: personid , unit:unitid},
success: function () {
// $('input[name=MailSent]').attr('checked', true);
$("#submitMail").removeAttr("disabled");
alert("Email sent!");
},
error: function () {
alert("Email not sent!");
}
});
}
It never comes to the error function. How do I make it go to the error? Any tips and suggestions are most welcome.
You can access your json response object by writing:
$.ajax({
url: url,
data: { person: personid , unit:unitid},
dataType: 'json',
success: function (response) {
if (response.success == false) {
// Error handling
} else {
// Success handling
}
},
error: function () {
alert("Email not sent!");
}
});
As Nick Bork already explained in a comment, the error/success status of a response is determined by the Http status code that is sent down in the header. You can still go the suggested way and inspect the response object and the success property but it is clearly not the proper way when you already have a more powerful and long proven mechanism (the HTTP protocol itself).
.NET will use HTTP 200 (OK) when everything goes according to the code but you can change this behaviour in the Controller by accessing the Reponse object like this, for example:
Response.StatusCode = 500; // or any of the other HTTP "failure" status codes
Any status code in the 4xx or 5xx category will trigger the error() handler specified in the $.ajax(...) call. From there you can of course also inspect the proper status code, the response details and every properties of the XHR object to provide a more meaningful user experience.
HTTP status codes are pretty much set in stone and are not likely to change, that's why they are in my opinion definitely preferrable to a custom made solution...
PS: For a list of HTTP status codes, wikipedia is your friend.

How can I maintain ModelState with RedirectToAction?

How can I return the result of a different action or move the user to a different action if there is an error in my ModelState without losing my ModelState information?
The scenario is; Delete action accepts a POST from a DELETE form rendered by my Index Action/View. If there is an error in the Delete I want to move the user back to the Index Action/View and show the errors that are stored by the Delete action in the ViewData.ModelState. How can this be done in ASP.NET MVC?
[AcceptVerbs(HttpVerbs.Post | HttpVerbs.Delete)]
public ActionResult Delete([ModelBinder(typeof(RdfUriBinder))] RdfUri graphUri)
{
if (!ModelState.IsValid)
return Index(); //this needs to be replaced with something that works :)
return RedirectToAction("Index");
}
Store your view data in TempData and retrieve it from there in your Index action, if it exists.
...
if (!ModelState.IsValid)
TempData["ViewData"] = ViewData;
RedirectToAction( "Index" );
}
public ActionResult Index()
{
if (TempData["ViewData"] != null)
{
ViewData = (ViewDataDictionary)TempData["ViewData"];
}
...
}
[EDIT] I checked the on-line source for MVC and it appears that the ViewData in the Controller is settable, so it is probably easiest just to transfer all of the ViewData, including the ModelState, to the Index action.
Use Action Filters (PRG pattern) (as easy as using attributes)
Mentioned here and here.
Please note that tvanfosson's solution will not always work, though in most cases it should be just fine.
The problem with that particular solution is that if you already have any ViewData or ModelState you end up overwriting it all with the previous request's state. For example, the new request might have some model state errors related to invalid parameters being passed to the action, but those would end up being hidden because they are overwritten.
Another situation where it might not work as expected is if you had an Action Filter that initialized some ViewData or ModelState errors. Again, they would be overwritten by that code.
We're looking at some solutions for ASP.NET MVC that would allow you to more easily merge the state from the two requests, so stay tuned for that.
Thanks,
Eilon
In case this is useful to anyone I used #bob 's recommended solution using PRG:
see item 13 -> link.
I had the additional issue of messages being passed in the VeiwBag to the View being written and checked / loaded manually from TempData in the controller actions when doing a RedirectToAction("Action"). In an attempt to simplify (and also make it maintainable) I slightly extended this approach to check and store/load other data as well. My action methods looked something like:
[AcceptVerbs(HttpVerbs.Post)]
[ExportModelStateToTempData]
public ActionResult ChangePassword(ProfileViewModel pVM) {
bool result = MyChangePasswordCode(pVM.ChangePasswordViewModel);
if (result) {
ViewBag.Message = "Password change success";
else {
ModelState.AddModelError("ChangePassword", "Some password error");
}
return RedirectToAction("Index");
}
And my Index Action:
[ImportModelStateFromTempData]
public ActionResult Index() {
ProfileViewModel pVM = new ProfileViewModel { //setup }
return View(pVM);
}
The code in the Action Filters:
// Following best practices as listed here for storing / restoring model data:
// http://weblogs.asp.net/rashid/archive/2009/04/01/asp-net-mvc-best-practices-part-1.aspx#prg
public abstract class ModelStateTempDataTransfer : ActionFilterAttribute {
protected static readonly string Key = typeof(ModelStateTempDataTransfer).FullName;
}
:
public class ExportModelStateToTempData : ModelStateTempDataTransfer {
public override void OnActionExecuted(ActionExecutedContext filterContext) {
//Only export when ModelState is not valid
if (!filterContext.Controller.ViewData.ModelState.IsValid) {
//Export if we are redirecting
if ((filterContext.Result is RedirectResult) || (filterContext.Result is RedirectToRouteResult)) {
filterContext.Controller.TempData[Key] = filterContext.Controller.ViewData.ModelState;
}
}
// Added to pull message from ViewBag
if (!string.IsNullOrEmpty(filterContext.Controller.ViewBag.Message)) {
filterContext.Controller.TempData["Message"] = filterContext.Controller.ViewBag.Message;
}
base.OnActionExecuted(filterContext);
}
}
:
public class ImportModelStateFromTempData : ModelStateTempDataTransfer {
public override void OnActionExecuted(ActionExecutedContext filterContext) {
ModelStateDictionary modelState = filterContext.Controller.TempData[Key] as ModelStateDictionary;
if (modelState != null) {
//Only Import if we are viewing
if (filterContext.Result is ViewResult) {
filterContext.Controller.ViewData.ModelState.Merge(modelState);
} else {
//Otherwise remove it.
filterContext.Controller.TempData.Remove(Key);
}
}
// Restore Viewbag message
if (!string.IsNullOrEmpty((string)filterContext.Controller.TempData["Message"])) {
filterContext.Controller.ViewBag.Message = filterContext.Controller.TempData["Message"];
}
base.OnActionExecuted(filterContext);
}
}
I realize my changes here are a pretty obvious extension of what was already being done with the ModelState by the code # the link provided by #bob - but I had to stumble on this thread before I even thought of handling it in this way.
Please don't skewer me for this answer. It is a legitimate suggestion.
Use AJAX
The code for managing ModelState is complicated and (probably?) indicative of other problems in your code.
You can pretty easily roll your own AJAX javascript code. Here is a script I use:
https://gist.github.com/jesslilly/5f646ef29367ad2b0228e1fa76d6bdcc#file-ajaxform
(function ($) {
$(function () {
// For forms marked with data-ajax="#container",
// on submit,
// post the form data via AJAX
// and if #container is specified, replace the #container with the response.
var postAjaxForm = function (event) {
event.preventDefault(); // Prevent the actual submit of the form.
var $this = $(this);
var containerId = $this.attr("data-ajax");
var $container = $(containerId);
var url = $this.attr('action');
console.log("Post ajax form to " + url + " and replace html in " + containerId);
$.ajax({
type: "POST",
url: url,
data: $this.serialize()
})
.done(function (result) {
if ($container) {
$container.html(result);
// re-apply this event since it would have been lost by the form getting recreated above.
var $newForm = $container.find("[data-ajax]");
$newForm.submit(postAjaxForm);
$newForm.trigger("data-ajax-done");
}
})
.fail(function (error) {
alert(error);
});
};
$("[data-ajax]").submit(postAjaxForm);
});
})(jQuery);
Maybe try
return View("Index");
instead of
return Index();

Categories

Resources