In my asp.net mvc application, I would like to show the user the error message that was used to throw an exception. The exception occurs in an ajax request. I have tried this:
In Global.asax.cs file, I have global application error handler:
protected void Application_Error(object sender, EventArgs e)
{
Exception exception = System.Web.HttpContext.Current.Server.GetLastError();
// do something so that client side gets the value exception.Message
// I have tried these, but no success
HttpContext.Current.Response.StatusDescription = exception.Message;
HttpContext.Current.Response.StatusCode = 1000; // custom status code
}
In javascript, I have global ajaxError handler:
$(document).ajaxError(function (xhr, props) {
// show the message in the exception thrown on the server side
});
I tried to get the exception message in JavaScript using props.statusText, props.responseText, but none of them had the exception message.
My question: What can I do in the Application_Error method so that I can get the message contained in exception.Message to the global ajaxError function on the client side? Please note that I can easily handle exception that occurs in any specific Action where an ajax request hits, but I would like to make a global exception handler, that will allow me to just throw an exception with a message from any part of my application, and the exception message gets shown to the user on the client side.
I have tried this, but this doesn't solve my problem, because I want to use jQuery global ajaxError, instead of handling error in just a specific ajax request. Can this be modified to achieve what I want?
You can create a basecontroller and override the OnException method
public class BaseController : Controller
{
protected override void OnException(ExceptionContext filterContext)
{
//your existing code to log errors here
filterContext.ExceptionHandled = true;
if (filterContext.HttpContext.Request.Headers["X-Requested-With"]
== "XMLHttpRequest")
{
filterContext.Result = new JsonResult
{
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
Data = new
{
Error = true,
Message = filterContext.Exception.Message
}
};
filterContext.HttpContext.Response.StatusCode = 500;
filterContext.ExceptionHandled = true;
}
else
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary
{{"controller", "Error"}, {"action", "Index"}});
}
}
}
and have all your controllers inherit from this
public class HomeController : BaseController
{
}
So any time an exception happens in your controllers, this OnException method will be executed and if it is an ajax request, It returns a json response with the below structure
{
Error : true,
Message : "The message from the exception caught"
}
and now in your javascript, wire up the global ajaxError event and you can read the response coming from server and parse it to a js object and then read the Message property
$(document).ready(function() {
$(document).ajaxError(function (event, request, settings) {
var d = JSON.parse(request.responseText);
alert("Ajax error:"+d.Message);
});
});
Related
I have a Java Spring web application that creates a list of roles that can be assigned to users. However, I am having an issue creating new roles which is invoked through an AJAX PUT call that returns a 405 error. The application is running on Java 8 and Spring 5.1.1.
I have tried debugging both the front end and back end side. What I found was, the call successfully reaches the back-end, processes the call through and returns. However, the front-end will claim that an error occurred and returns a 405 error. But the issue is, the error does not provide any details on what is failing exactly. The most information I could find was this message:
TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them
at Function.invokeGetter (<anonymous>:2:14)
at Object.error (http://localhost:8000/xxx/admin-user-search.html:1011:10)
at fire (http://localhost:8000/xxxx/webjars/jquery/3.1.1/jquery.js:3305:31)
at Object.fireWith [as rejectWith] (http://localhost:8000/xxxx/webjars/jquery/3.1.1/jquery.js:3435:7)
at done (http://localhost:8000/xxxx/webjars/jquery/3.1.1/jquery.js:9244:14)
at XMLHttpRequest.<anonymous> (http://localhost:8000/xxxx/webjars/jquery/3.1.1/jquery.js:9484:9)
Javascript:
function submitCreateNewRole(){
isBlank = false;
var myData;
newRoleName = $('#modalUserRoleSearchText').val();
newRoleDescription = $('#modelUserRoleDescText').val();
if (newRoleName=='' || newRoleDescription==''){
isBlank = true;
}
if (isBlank){
appAPI.setErrorBannerRole("Blank data is not allowed. Please enter non-blank data to create new Role.");
} else {
var UserRoleSearchModel = {};
var userRoleAction = "createNewUserRole" ;
RoleModel.ldapName = newRoleName;
RoleModel.roleDesc = newRoleDescription;
var token = $("meta[name='_csrf']").attr("content");
var URL = "json/admin-user-search?userRoleAction=" + userRoleAction + "&roleName=" + newRoleName + "&roleDesc=" + newRoleDescription;
var req = JSON.stringify(RoleModel);
var jqxhr = $.ajax({
type: "PUT",
url: URL,
headers: { "X-CSRF-TOKEN" : token },
data: req,
contentType: "application/json",
error: function (xhr, status, error) {
console.log("Failure caught");
console.log(xhr.responseText);
},
success: function(data){
myData = data;
}
}).done(function( msg ) {
$('#alertMessageSuccess').val('Successfully create new row');
}).fail(function(jqxhr) {
$('#alertMessageError').val('failed to create role' + newRoleName);
});
}
return myData;
}
Java Spring:
#RequestMapping(value = {
"admin-user-search"
}, method = RequestMethod.PUT)
public ModelAndView createNewUserRole(#AuthenticationPrincipal Principal principal,
#RequestParam(required = false) String pageCommand,
#ModelAttribute("UserModel") UserModel userSearch,
#ModelAttribute("RoleModel") RoleModel userRoleSearch,
#RequestParam(value = "roleName", required = false) String roleName,
#RequestParam(value = "roleDesc", required = false) String roleDesc,
#RequestParam(value = "userRoleAction", required = false) String userRoleCommmand, HttpServletRequest request) {
Results results = null;
List<Role> roleVOs = null;
String roleResponseMessage;
ModelAndView rView = new ModelAndView("admin-user-search");
if ("createNewUserRole".equals(userRoleCommmand)) {
userRoleSearch.clearAlertMessages();
userSearch.clearAlertMessage();
if ("".equals(roleName)) {
roleResponseMessage = "Unable to create a new role due to invalid or blank LDAP username enterred. Please try again with valid LDAP username.";
userRoleSearch.setErrorMessages(roleResponseMessage);
} else if ("".equals(roleDesc)) {
roleResponseMessage = "Unable to create a new role due to invalid or blank Role Description entered.";
userRoleSearch.setErrorMessages(roleResponseMessage);
} else {
try {
this.tdmcRoleDao.addNewRole(roleName, roleDesc);
roleResponseMessage = String.format("New user role '%s' has been added.", userRoleSearch.getLdapDn());
userRoleSearch.setSuccessMessages(roleResponseMessage);
userSearch.setSuccessMessages(roleResponseMessage);
roleVOs = retrieveAllRoles();
} catch (final SQLException e) {
LOGGER.error(e, TDMCMessages.TDMC_0142_DATABASE_INSERT_EXCEPTION, "tdmcRoleDao.addNewRole(newRoleLdap)");
roleResponseMessage = "Unable to create a new role -'%s' due to DB problem. Please retry with a new valid role name.";
userRoleSearch.setErrorMessages(roleResponseMessage);
userSearch.setErrorMessages(roleResponseMessage);
} catch (final DuplicateKeyException dupEx) {
roleResponseMessage = "Unable to create a duplicate role'. Please retry with non-duplicated role name.";
userRoleSearch.setErrorMessages(roleResponseMessage);
userSearch.setErrorMessages(roleResponseMessage);
}
if (roleVOs != null && !roleVOs.isEmpty()) {
results = populateRolesToResults(roleVOs);
}
userRoleSearch.setResults(results);
userRoleSearch.setSelected(roleVOs);
rView.addObject("RoleModel", userRoleSearch);
}
}
return rView;
}
When I run the application and try to create a new Role, I see that the PUT call reaches the Java server and successfully returns the view. However, on the Web client side, it throws the 405 error, and it's not clear what exactly is failing. Any insight would be very helpful.
On another note, the application also makes POST and GET calls as well, but those seem to work fine, so I cannot understand why the PUT calls are failing in this case.
EDIT: Fix code
first of all your url seems to be wrong, please check.
and change to post mapping, then post through body, something
like #requesrbody
I have a function that triggers on a button click that passes the following argument: insertSongPlay(newSong.songID); when I console.log the newSong.songID I see a value of 90, which is desired.
This function is called here which runs an ajax call:
function insertSongPlay(songID)
{
$.ajax
({
type: "POST",
contentType: "application/json; charset=utf-8",
dataType: "json",
url: "/Album/InsertSongPlay/",
data: JSON.stringify({ "songID": songID }),
success: function(data)
{
console.log("submitted");
console.log(songID);
//TODO: Indicate Success
},
error: function(jqXHR, textStatus, errorThrown)
{
//TODO: Indicate Error
console.log(errorThrown);
}
});
}
Here is the corresponding ajax url found in my AlbumController:
public JsonResult InsertSongPlay(int songID)
{
try
{
EntityDataAccess.InsertSongPlay(songID);
return Json(true);
}
catch(Exception ex)
{
return Json(ex);
}
}
Then in my EntityDataAccess.cs the following is run from the InsertSongPlay entity data access:
public static SongPlayDaily InsertSongPlay(int songID)
{
using(var Context = GetContext())
{
var currentSongPlay = Context.SongPlayDailies.FirstOrDefault(x => x.SongID == songID && x.PlayDate == DateTime.Now.Date);
if (currentSongPlay != null)
currentSongPlay.NumberOfPlays++;
else
{
currentSongPlay = new SongPlayDaily();
currentSongPlay.SongID = songID;
currentSongPlay.PlayDate = DateTime.Now.Date;
currentSongPlay.NumberOfPlays = 1;
Context.SongPlayDailies.Add(currentSongPlay);
}
Context.SaveChanges();
return currentSongPlay;
}
}
However, my ajax call always throws an error and prints Internal Server Error to the console log. The Visual Studio debug mode indicates some sort of circular reference error. I'm not quite sure where I'm going wrong.
Thanks!
As we figured out in comments - there are multiple problems here. First,
return Json(ex);
Is a bad idea. That will inspect all properties of exception and try to serialize them, and all properties of those properties, to json. As you see, that is not going well, and also completely unnecessary. If you want to return error response to client - prepare it (use ex.Message for example). Plus, you don't want to return sensetive information (like stack trace) to your clients on every error.
Besides that - by doing return Json() on exception - you violate HTTP conventions. Json() will return response with status code 200 OK. But in your case it's not really "ok" - so you should return another status code indicating error. If not that circular reference error - your code in ajax "error" handler would never has been triggered, even when there was really an error.
Easiest way to fix it is just remove try-catch completely and let framework handle that for you.
Second problem is
x.PlayDate == DateTime.Now.Date
filter in query. Entity Framework has problem with accessing Date property of DateTime objects and refuses to convert that into SQL query. You can either move that to another variable:
var today = DateTime.Now.Date;
Where(x => x.PlayDate == today)
or use DbFunctions.TruncateTime:
Where(x => x.PlayDate == DbFunctions.TruncateTime(DateTime.Now))
Note that those ways are not exactly equivalent. First will compare with exact date value, and second will use SQL SYSDATETIME() function. In this case that hardly matters though.
Is it because you're missing the [HttpPost] attribute over your controller action?
[HttpPost]
public JsonResult InsertSongPlay(int songID)
{
try
{
EntityDataAccess.InsertSongPlay(songID);
return Json(true);
}
catch(Exception ex)
{
return Json(ex);
}
}
in post action you should use complex type for receive data from client.
try this
[HttpPost]
public JsonResult InsertSongPlay(SongModel songModel)
{
try
{
EntityDataAccess.InsertSongPlay(songModel.songID);
return Json(true);
}
catch(Exception ex)
{
return Json(ex);
}
}
public class SongModel{
public int songID {get;set}
}
I've been struggling to get around this problem for quite a while now but I cannot seem to find a solution that works for me.
I handle all errors by overriding OnException method in my BaseController class, which all others controllers inherit.
protected override void OnException(ExceptionContext filterContext)
{
filterContext.ExceptionHandled = true;
var rd = new RouteData
{
Values = { ["controller"] = "Error", ["action"] = "Index" },
DataTokens = { }
};
var error = $#"Error: {filterContext.Exception.Message} in {filterContext.HttpContext.Request.FilePath}
Details:
{filterContext.Exception.StackTrace}";
_logger = LogManager.GetLogger(GetType());
_logger.Error(error + Environment.NewLine + "Temp Id: " + AppSession.TempId);
IController c = new ErrorController();
c.Execute(new RequestContext(new HttpContextWrapper(System.Web.HttpContext.Current), rd));
}
My error controller is pretty simple:
public ActionResult Index()
{
ViewBag.Error = "Oops.. Something went wrong";
return View("Error");
}
It works, Error page shows up, but it always loads up inside the partial view container, the partial view that raised the error. Instead, I want to do a proper redirect to just error page alone.
I've tried using and handle errors that way but it behaves in exact same manner. I've also tried handling errors in Global.asax Application_Error method, which I knew wouldn't make any difference but I wanted
to try it anyways..
My guess is because the partial view is loaded via $.get call it somehow wraps the response in the same div/container the partial view was supposed to load.
Any help would be greatly appreciated. Should you need more information, please let me know.
I've also tried looking up on SO for similar scenarios but no post, that i've found, has a good solution...
Thanks in advance.
What you should be doing is, If the error happens in an ajax call, you should be sending a json response with a property which indicates which url to redirect to. If it is not an ajax request, you can send the normal redirectresult.
Something like this
protected override void OnException(ExceptionContext filterContext)
{
//your existing code to log errors here
filterContext.ExceptionHandled = true;
if (filterContext.HttpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest")
{
var targetUrl = UrlHelper.GenerateUrl("Default", "Index", "Error",
new RouteValueDictionary(), RouteTable.Routes, Request.RequestContext, false);
filterContext.Result = new JsonResult
{
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
Data = new { Error = true, Message = filterContext.Exception.Message,
RedirectUrl= targetUrl }
};
filterContext.HttpContext.Response.StatusCode = 500;
filterContext.ExceptionHandled = true;
}
else
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary
{{"controller", "Error"}, {"action", "Index"}});
}
}
Now you can listen the .ajaxError() event which will be fired anytime an ajax request completes with an error. Get the RedirectUrl value and redirect as needed. You may also consider showing a meaningful message to user (Even in a partial view from the modal dialog) so user won't be confused by the blind redirect !
$(function () {
$(document).ajaxError(function (event, request, settings) {
console.log('ajax request', request.responseText);
var d = JSON.parse(request.responseText);
alert("Ajax error:"+d.Message);
window.location.href = d.RedirectUrl;
});
});
I want to pass error message to Blueimp jQuery File Upload plugin. I use ASP.NET MVC and throw my own exception when some conditions are appeared (i.e. file is not real image, only image exception or image is too wide etc).
var file = Request.Files[i];
_service.ImageValidation(file.InputStream);
public void ImageValidation(System.IO.Stream fileStream)
{
Bitmap bmp = null;
try
{
bmp = new Bitmap(fileStream, false);
}
catch
{
throw new NoImageException();
}
if (bmp.Width > bmp.Height && (bmp.Width < 1024 || bmp.Height < 768))
throw new ImageDimensionTooSmall();
if ((bmp.Width <= bmp.Height) && (bmp.Width < 768 || bmp.Height < 1024))
throw new ImageDimensionTooSmall();
fileStream.Position = 0;
}
on client side I try to catch error by the following way:
$('#fileupload').fileupload({
url: '/SmartphonePhotographer/ManageFiles?ResponseID=' + ResponseID,
error: function (e, data) {
alert('error');
}
});
'data' variable always has 'error' value. 'e' has many properties, including statusText='Internal server error' and responseText (html page with exception). Question - how can I pass error message on server side to catch it on client side (maybe, there is an json format for errors, but I did not find it in documentation)
It goes to the error event because you are throwing an exception in your server side code. So the ajax call is getting a 500 internal error response.
What you can do is, instead of throwing an exception, return a json response with the error messages.
[HttpPost]
public ActionResult SaveImage()
{
if(IsFileNotValid()) //your method to validate the file
{
var customErrors = new List<string> {"File format is not good",
"File size is too bib"};
return Json(new { Status = "error", Errors = customErrors });
}
//Save/Process the image
return Json ( new { Status="success", Message="Uploaded successfully" });
}
And in the done() event, you can inspect the json response and show the error messages as needed.
$('#fileupload').fileupload({
url: '/SmartphonePhotographer/ManageFiles?ResponseID=' + ResponseID,
error: function (e, data,txt) {
alert('error' + txt);
}
}).done(function(response){
if(response.Status==="error")
{
$.each(services.Errors, function (a, b) {
alert(b);
});
}
});
With this approach, you can send multiple validation errors back to the client and client can process(show to user ?) it.
MVC 6
In MVC6, you can return an HttpStatusCode response directly from the MVC controller action. So no need to send a JSON response yourself.
[HttpPost]
public IActionResult SaveImage()
{
var customErrors = new List<string> { "File format is not good",
"File size is too bib" };
return HttpBadRequest(customErrors);
}
This will send a 400 Response to the caller with the data we passed(the list of errors) in the response. So you can access the responseJSON property of your error xHr object of the error event to get it
error: function (a, b, c) {
$.each(a.responseJSON, function (a, b) {
alert(b);
});
}
I agree your issue is that you are throwing an exception versus returning a controlled response. Most frameworks look for status codes in the 400x or 500x. So you want to return a friendly json object and a status code in those ranges. If you do that your data object in the error block will be what you returned.
MVC Land:
//get a reference to request and use the below.
return this.Request.CreateResponse(HttpStatusCode.BadRequest, "Your message here");
Web Api 2
Use an IHttpActionResult and return BadRequest("My error message"); If you do that it will set your status code and return the response as the data.
I am using angularJS and spring 3.2.4 for REST exception handling and handling exception like this
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(value=HttpStatus.BAD_REQUEST)
#ResponseBody
public ErrorFormInfo handleMethodArgumentNotValid(HttpServletRequest req, MethodArgumentNotValidException ex, HttpServletResponse response) {
String errorMessage = "gender sring is out of range";
String errorURL = req.getRequestURL().toString();
System.out.println("Throwing exception from exception handler");
ErrorFormInfo errorInfo = new ErrorFormInfo(errorURL, errorMessage);
return errorInfo;
}
whenever sent argument validation fails it throws a MethodArgumentNotValidException and correct me if i am wrong that spring 3.2 converts the objects in json format automatically
but i am not able to get the object in error block of angularJs. It throws the error saying "Response is undefined" with this output
completeRequest(callback=done(status, response, headersString), status=400, response="{"errorMessage":"http:/...sring is out of range"}", headersString="Server: Apache-Coyote/1...MT\r\nConnection: close\r\n")angular-1.0.7.js (line 9333)
onreadystatechange()
I suspect that object is not being converted into JSON because is has strange header string.
Below is my JS code
$http.post('rest/create?cd=' + (new Date()).getTime(),
XYZ
.success(function(data) {
alert('Data added successfully');
}).error(function(data) {
var errorInfo = data;
alert("data from server side"+errorInfo.errorMessage);
alert("Unable to process");
});
Please help...Thanks in advance
There was an interceptor injected in $httpProvider service of angularJs which run only for success and error. so I wrote a the piece of code in promise to handle response 'undefined'
case. Code is as follows:
if(jsonStr.ERROR_URI != 'undefined')
{
var jsonStr = response.data;
console.log('inside error block');
console.log('response.status'+response.status);
console.log('response.data'+response.data);
console.log('response.data.ERROR_URI'+jsonStr.ERROR_URI);
return $q.reject(response);
}
it simply rejects the response if it is undefined. It solved my problem.