ASP.NET MVC Exception Handling with AJAX/JSON - asp.net

I have several methods in a controller that look like:
[HttpPost]
public ActionResult AddEditCommentToInvoice(string invoiceNumber, string comments)
{
var response = new { success = true, msg = "Comment saved", statusMsg = "Comment saved" };
try
{
var recordsModified = invoiceService.AddCommentsToInvoice(invoiceNumber, comments);
Log.Info(recordsModified ? "Updated Comment" : "Did not update Comment");
} catch (Exception ex) {
Response.StatusCode = (int)HttpStatusCode.InternalServerError;
return Json(new {
success = false,
msg = "There is missing field data",
statusMsg = ex.Message
}, JsonRequestBehavior.AllowGet);
}
return Json(response, JsonRequestBehavior.AllowGet);
}
While this code works, I'm not comfortable with this approach because:
Try/Catches are expensive
The code catches System.Exception
The code is ugly
Now I know that I can use OnException or the HandleError attribute.
I also did some research on ELMAH and this looks promising.
But I still want to return JSON via AJAX to my user to indicate whether the operation was a success or not.
So my question is, has anyone used any of the three methods (or specifically ELMAH) to return JSON via AJAX?

I use another approach that's an approach that can be applied at the controller level or globally through GlobalFilters. In my MVC controllers, you could override OnActionExecuted method, and do this:
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (filterContext.Exception != null)
{
filterContext.Result = Json(new { success = false });
return;
}
base.OnActionExecuted(filterContext);
}
This could also be done as an action filter attribute. You wouldn't need any exception handling in your controllers - if an exception occurs, then this is handled within the context of the result.

Related

Can't return completedsuccesfull task in .net core

Hi I wrote the following code:
private bool GetIsCompleted()
{
return Email.SendMessageAsync().IsCompletedSuccessfully;
}
[HttpPost]
public ViewResult CheckOut(Order order)
{
if (Cart.Lines.Count() == 0)
{
ModelState.AddModelError("","Your Cart is empty!");
}
if (ModelState.IsValid)
{
order.CartLines = Cart.Lines;
order.DateTime = DateTime.Now;
order.TotalPrice = Cart.ComputeTotalValue();
if (Repository.SaveOrder(order))
{
if (User.Identity.Name != null)
{
Email.SetMessageBody(order.OrderID);
if (GetIsCompleted())
{
Cart.Clear();
return View("Completed");
}
}
}
ViewBag.Error = "An error Occured while sending you an email with the order details.";
return View(new Order());
}
else
{
ViewBag.Error = "An error Occured while trying to save your order. Please try again!";
return View(new Order());
}
}
public async Task SendMessageAsync()
{
this.Message = new MailMessage(this.MailFrom.ToString(), this.MailTo.ToString(), this.GetSubject(), this.GetMessageBody());
//Message.Dispose();
try
{
await this.Client.SendMailAsync(this.Message);
}
catch (Exception ex)
{
Logger.LogInformation("The Email couldn't send to the recipient");
}
}
I get
An error Occured while sending you an email with the order details.
in the View. I want GetIsCompleted() to return true to proceed the code. It is developed under .net core. I do not understand why IsCompletedSuccessfully() does not return true; Any suggestion?
The current flow of your code is this:
Start sending the e-mail.
Check if it is completed successfully, decide that it hasn't and return failure.
The e-mail completes sending.
You're awaiting the actual SendMailAsync(..) method, and that's great, but nothing awaits SendMessageAsync(...) so it immediately returns the incomplete task to the caller. Because there isn't enough time between starting to send the e-mail and checking if the task completed, the status will be false.
You need to use async all the way up. Change your method definition to be async:
public async Task<ViewResult> CheckOut(Order order)
Replace this code:
if (GetIsCompleted())
{
Cart.Clear();
return View("Completed");
}
with this:
try
{
await Email.SendMessageAsync();
Cart.Clear();
return View("Completed");
}
catch (Exception e)
{
// handle exception
}
It's worth noting that you'll only ever get an exception if the call to new MailMessage(...) fails because your try/catch block in SendMessageAsync is swallowing all other exceptions.

Why doesn't Controller Factory use a Controller returned by that factory?

I implemented a custom controller factory in ASP.NET MVC, and I registered it in global.ascx. The idea is to handle the case of 404 and also exceptions in the controller constructors. I know the factory has been assigned to ASP.NET MVC, because on requests, I can step into it. I can see that I'm returning the controller that I think. But why, oh why on earth, is not my controller used? But I'd think I'd get the usual action not found exception, not controller..conceptually I'm wondering if this is even the right spot to do this in.
protected override IController GetControllerInstance
(RequestContext context,
Type controllerType)
{
IController controller = null;
try
{
controller = base.GetControllerInstance(context, controllerType);
}
catch (CurrentSessionException)
{
controller = new LoginController();
}
catch (System.Web.HttpException)
{
controller = new ErrorController();
}
catch (System.Exception)
{
controller = new ErrorController();
}
return controller;
}
Try manually clearing the errors in your catch statement.
requestContext.HttpContext.ClearError();
Ideally this is best handled as a Filter. MVC comes with a HandleErrorAttribute which you can subclass. You would override the OnException method and then simple handle the logic as you wish.
This is what MVC 3 does by default.
public virtual void OnException(ExceptionContext filterContext) {
if (filterContext == null) {
throw new ArgumentNullException("filterContext");
}
if (filterContext.IsChildAction) {
return;
}
// If custom errors are disabled, we need to let the normal ASP.NET exception handler
// execute so that the user can see useful debugging information.
if (filterContext.ExceptionHandled || !filterContext.HttpContext.IsCustomErrorEnabled) {
return;
}
Exception exception = filterContext.Exception;
// If this is not an HTTP 500 (for example, if somebody throws an HTTP 404 from an action method),
// ignore it.
if (new HttpException(null, exception).GetHttpCode() != 500) {
return;
}
if (!ExceptionType.IsInstanceOfType(exception)) {
return;
}
string controllerName = (string)filterContext.RouteData.Values["controller"];
string actionName = (string)filterContext.RouteData.Values["action"];
HandleErrorInfo model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);
filterContext.Result = new ViewResult {
ViewName = View,
MasterName = Master,
ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
TempData = filterContext.Controller.TempData
};
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.StatusCode = 500;
// Certain versions of IIS will sometimes use their own error page when
// they detect a server error. Setting this property indicates that we
// want it to try to render ASP.NET MVC's error page instead.
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
}

Trapping the error message inside jquery ajax - from asp.net mvc action

I was able to get the actual error message before when I was using jquery ajax+ asp.net web services. However, the same code inside jquery $ajax error no longer works.
Inside my .js I have
$.ajax({
contentType: 'application/json, charset=utf-8',
type: "POST",
url: "/Controller/DoSomething",
data: JSON.stringify({ varname: varvalue }),
cache: false,
dataType: "json",
success: function (wo) {
alert('yay!');
},
error: function (xhr) {
alert('error');
if (xhr.responseText) {
var err = JSON.parse(xhr.responseText);
if (err) {
alert(err.Message);
}
else {
alert("Unknown server error, please try again!");
}
}
}
});
Inside my Controller I have
public JsonResult DoSomething(string folderno)
{
CustomObject obj;
//get the obj value from repository here
throw new Exception("my test error message");
return Json(obj);
}
I looked at the Firebug and it appears that I am getting
"JSON.parse: unexpected character" error.
What I am trying to do here is to fake a situation when getting obj from repository throws an exception. Obviously, return Json(obj) never gets reached.
My question is, how do I deal with this situation and trap the error messages on the JS side? Do I need to do something in my controller?
In my earlier set up of Jquery+asp.net web services, I could throw an exception inside my web service method (as shown in my action now) and it would be trapped in my ajax error and the error message would be parsed out.
Now, it would appear that I need to catch the exception and pack in myself....question is how? And do I need to do this inside every action? This seems like a lot of work.
One thing I do is create a generic return object for AJAX calls.
Something like:
public class AJAXReturn
{
public string Message { get; set; }
public object Result { get; set; }
}
Then in your return functions wrap them in Exceptions (or create a generic exception handler) that will look something like:
public JsonResult DoSomething(string folderno)
{
CustomObject obj = new { FolderNo = folderno };
AJAXReturn result;
try
{
result.Message = "OK";
result.Result = obj;
}
catch (Exception ex)
{
result.Message = "ERROR";
result.Result = ex;
}
finally
{
return Json(result);
}
}
Edit: On the javascript side, just check your result for data.Message == 'OK'. If it isn't ok you can display either the specific exception info or anything you want.
Edit 2: Sorry I should've mentioned this will always return in the success callback so make sure you parse it there.

App-wide error handling for ASP.NET MVC2 web app? [duplicate]

I have some basic code to determine errors in my MVC application. Currently in my project I have a controller called Error with action methods HTTPError404(), HTTPError500(), and General(). They all accept a string parameter error. Using or modifying the code below.
What is the best/proper way to pass the data to the Error controller for processing? I would like to have a solution as robust as possible.
protected void Application_Error(object sender, EventArgs e)
{
Exception exception = Server.GetLastError();
Response.Clear();
HttpException httpException = exception as HttpException;
if (httpException != null)
{
RouteData routeData = new RouteData();
routeData.Values.Add("controller", "Error");
switch (httpException.GetHttpCode())
{
case 404:
// page not found
routeData.Values.Add("action", "HttpError404");
break;
case 500:
// server error
routeData.Values.Add("action", "HttpError500");
break;
default:
routeData.Values.Add("action", "General");
break;
}
routeData.Values.Add("error", exception);
// clear error on server
Server.ClearError();
// at this point how to properly pass route data to error controller?
}
}
Instead of creating a new route for that, you could just redirect to your controller/action and pass the information via querystring. For instance:
protected void Application_Error(object sender, EventArgs e) {
Exception exception = Server.GetLastError();
Response.Clear();
HttpException httpException = exception as HttpException;
if (httpException != null) {
string action;
switch (httpException.GetHttpCode()) {
case 404:
// page not found
action = "HttpError404";
break;
case 500:
// server error
action = "HttpError500";
break;
default:
action = "General";
break;
}
// clear error on server
Server.ClearError();
Response.Redirect(String.Format("~/Error/{0}/?message={1}", action, exception.Message));
}
Then your controller will receive whatever you want:
// GET: /Error/HttpError404
public ActionResult HttpError404(string message) {
return View("SomeView", message);
}
There are some tradeoffs with your approach. Be very very careful with looping in this kind of error handling. Other thing is that since you are going through the asp.net pipeline to handle a 404, you will create a session object for all those hits. This can be an issue (performance) for heavily used systems.
To answer the initial question "how to properly pass routedata to error controller?":
IController errorController = new ErrorController();
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
Then in your ErrorController class, implement a function like this:
[AcceptVerbs(HttpVerbs.Get)]
public ViewResult Error(Exception exception)
{
return View("Error", exception);
}
This pushes the exception into the View. The view page should be declared as follows:
<%# Page Language="C#" Inherits="System.Web.Mvc.ViewPage<System.Exception>" %>
And the code to display the error:
<% if(Model != null) { %> <p><b>Detailed error:</b><br /> <span class="error"><%= Helpers.General.GetErrorMessage((Exception)Model, false) %></span></p> <% } %>
Here is the function that gathers the all exception messages from the exception tree:
public static string GetErrorMessage(Exception ex, bool includeStackTrace)
{
StringBuilder msg = new StringBuilder();
BuildErrorMessage(ex, ref msg);
if (includeStackTrace)
{
msg.Append("\n");
msg.Append(ex.StackTrace);
}
return msg.ToString();
}
private static void BuildErrorMessage(Exception ex, ref StringBuilder msg)
{
if (ex != null)
{
msg.Append(ex.Message);
msg.Append("\n");
if (ex.InnerException != null)
{
BuildErrorMessage(ex.InnerException, ref msg);
}
}
}
I found a solution for ajax issue noted by Lion_cl.
global.asax:
protected void Application_Error()
{
if (HttpContext.Current.Request.IsAjaxRequest())
{
HttpContext ctx = HttpContext.Current;
ctx.Response.Clear();
RequestContext rc = ((MvcHandler)ctx.CurrentHandler).RequestContext;
rc.RouteData.Values["action"] = "AjaxGlobalError";
// TODO: distinguish between 404 and other errors if needed
rc.RouteData.Values["newActionName"] = "WrongRequest";
rc.RouteData.Values["controller"] = "ErrorPages";
IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory();
IController controller = factory.CreateController(rc, "ErrorPages");
controller.Execute(rc);
ctx.Server.ClearError();
}
}
ErrorPagesController
public ActionResult AjaxGlobalError(string newActionName)
{
return new AjaxRedirectResult(Url.Action(newActionName), this.ControllerContext);
}
AjaxRedirectResult
public class AjaxRedirectResult : RedirectResult
{
public AjaxRedirectResult(string url, ControllerContext controllerContext)
: base(url)
{
ExecuteResult(controllerContext);
}
public override void ExecuteResult(ControllerContext context)
{
if (context.RequestContext.HttpContext.Request.IsAjaxRequest())
{
JavaScriptResult result = new JavaScriptResult()
{
Script = "try{history.pushState(null,null,window.location.href);}catch(err){}window.location.replace('" + UrlHelper.GenerateContentUrl(this.Url, context.HttpContext) + "');"
};
result.ExecuteResult(context);
}
else
{
base.ExecuteResult(context);
}
}
}
AjaxRequestExtension
public static class AjaxRequestExtension
{
public static bool IsAjaxRequest(this HttpRequest request)
{
return (request.Headers["X-Requested-With"] != null && request.Headers["X-Requested-With"] == "XMLHttpRequest");
}
}
I struggled with the idea of centralizing a global error handling routine in an MVC app before. I have a post on the ASP.NET forums.
It basically handles all your application errors in the global.asax without the need for an error controller, decorating with the [HandlerError] attribute, or fiddling with the customErrors node in the web.config.
Perhaps a better way of handling errors in MVC is to apply the HandleError attribute to your controller or action and update the Shared/Error.aspx file to do what you want. The Model object on that page includes an Exception property as well as ControllerName and ActionName.
This may not be the best way for MVC ( https://stackoverflow.com/a/9461386/5869805 )
Below is how you render a view in Application_Error and write it to http response. You do not need to use redirect. This will prevent a second request to server, so the link in browser's address bar will stay same. This may be good or bad, it depends on what you want.
Global.asax.cs
protected void Application_Error()
{
var exception = Server.GetLastError();
// TODO do whatever you want with exception, such as logging, set errorMessage, etc.
var errorMessage = "SOME FRIENDLY MESSAGE";
// TODO: UPDATE BELOW FOUR PARAMETERS ACCORDING TO YOUR ERROR HANDLING ACTION
var errorArea = "AREA";
var errorController = "CONTROLLER";
var errorAction = "ACTION";
var pathToViewFile = $"~/Areas/{errorArea}/Views/{errorController}/{errorAction}.cshtml"; // THIS SHOULD BE THE PATH IN FILESYSTEM RELATIVE TO WHERE YOUR CSPROJ FILE IS!
var requestControllerName = Convert.ToString(HttpContext.Current.Request.RequestContext?.RouteData?.Values["controller"]);
var requestActionName = Convert.ToString(HttpContext.Current.Request.RequestContext?.RouteData?.Values["action"]);
var controller = new BaseController(); // REPLACE THIS WITH YOUR BASE CONTROLLER CLASS
var routeData = new RouteData { DataTokens = { { "area", errorArea } }, Values = { { "controller", errorController }, {"action", errorAction} } };
var controllerContext = new ControllerContext(new HttpContextWrapper(HttpContext.Current), routeData, controller);
controller.ControllerContext = controllerContext;
var sw = new StringWriter();
var razorView = new RazorView(controller.ControllerContext, pathToViewFile, "", false, null);
var model = new ViewDataDictionary(new HandleErrorInfo(exception, requestControllerName, requestActionName));
var viewContext = new ViewContext(controller.ControllerContext, razorView, model, new TempDataDictionary(), sw);
viewContext.ViewBag.ErrorMessage = errorMessage;
//TODO: add to ViewBag what you need
razorView.Render(viewContext, sw);
HttpContext.Current.Response.Write(sw);
Server.ClearError();
HttpContext.Current.Response.End(); // No more processing needed (ex: by default controller/action routing), flush the response out and raise EndRequest event.
}
View
#model HandleErrorInfo
#{
ViewBag.Title = "Error";
// TODO: SET YOUR LAYOUT
}
<div class="">
ViewBag.ErrorMessage
</div>
#if(Model != null && HttpContext.Current.IsDebuggingEnabled)
{
<div class="" style="background:khaki">
<p>
<b>Exception:</b> #Model.Exception.Message <br/>
<b>Controller:</b> #Model.ControllerName <br/>
<b>Action:</b> #Model.ActionName <br/>
</p>
<div>
<pre>
#Model.Exception.StackTrace
</pre>
</div>
</div>
}
Application_Error having issue with Ajax requests. If error handled in Action which called by Ajax - it will display your Error View inside the resulting container.
Brian,
This approach works great for non-Ajax requests, but as Lion_cl stated, if you have an error during an Ajax call, your Share/Error.aspx view (or your custom error page view) will be returned to the Ajax caller--the user will NOT be redirected to the error page.
Use Following code for redirecting on route page.
Use exception.Message instide of exception. Coz exception query string gives error if it extends the querystring length.
routeData.Values.Add("error", exception.Message);
// clear error on server
Server.ClearError();
Response.RedirectToRoute(routeData.Values);
I have problem with this error handling approach:
In case of web.config:
<customErrors mode="On"/>
The error handler is searching view Error.shtml
and the control flow step in to Application_Error global.asax only after exception
System.InvalidOperationException: The view 'Error' or its master was
not found or no view engine supports the searched locations. The
following locations were searched: ~/Views/home/Error.aspx
~/Views/home/Error.ascx ~/Views/Shared/Error.aspx
~/Views/Shared/Error.ascx ~/Views/home/Error.cshtml
~/Views/home/Error.vbhtml ~/Views/Shared/Error.cshtml
~/Views/Shared/Error.vbhtml at
System.Web.Mvc.ViewResult.FindView(ControllerContext context)
....................
So
Exception exception = Server.GetLastError();
Response.Clear();
HttpException httpException = exception as HttpException;
httpException is always null then
customErrors mode="On"
:(
It is misleading
Then <customErrors mode="Off"/> or <customErrors mode="RemoteOnly"/> the users see customErrors html,
Then customErrors mode="On" this code is wrong too
Another problem of this code that
Response.Redirect(String.Format("~/Error/{0}/?message={1}", action, exception.Message));
Return page with code 302 instead real error code(402,403 etc)

Async calls in WP7

I have been experimenting with WP7 apps today and have hit a bit of a wall.
I like to have seperation between the UI and the main app code but Ive hit a wall.
I have succesfully implemented a webclient request and gotten a result, but because the call is async I dont know how to pass this backup to the UI level. I cannot seem to hack in a wait for response to complete or anything.
I must be doing something wrong.
(this is the xbox360Voice library that I have for download on my website: http://www.jamesstuddart.co.uk/Projects/ASP.Net/Xbox_Feeds/ which I am porting to WP7 as a test)
here is the backend code snippet:
internal const string BaseUrlFormat = "http://www.360voice.com/api/gamertag-profile.asp?tag={0}";
internal static string ResponseXml { get; set; }
internal static WebClient Client = new WebClient();
public static XboxGamer? GetGamer(string gamerTag)
{
var url = string.Format(BaseUrlFormat, gamerTag);
var response = GetResponse(url, null, null);
return SerializeResponse(response);
}
internal static XboxGamer? SerializeResponse(string response)
{
if (string.IsNullOrEmpty(response))
{
return null;
}
var tempGamer = new XboxGamer();
var gamer = (XboxGamer)SerializationMethods.Deserialize(tempGamer, response);
return gamer;
}
internal static string GetResponse(string url, string userName, string password)
{
if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password))
{
Client.Credentials = new NetworkCredential(userName, password);
}
try
{
Client.DownloadStringCompleted += ClientDownloadStringCompleted;
Client.DownloadStringAsync(new Uri(url));
return ResponseXml;
}
catch (Exception ex)
{
return null;
}
}
internal static void ClientDownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error == null)
{
ResponseXml = e.Result;
}
}
and this is the front end code:
public void GetGamerDetails()
{
var xboxManager = XboxFactory.GetXboxManager("DarkV1p3r");
var xboxGamer = xboxManager.GetGamer();
if (xboxGamer.HasValue)
{
var profile = xboxGamer.Value.Profile[0];
imgAvatar.Source = new BitmapImage(new Uri(profile.ProfilePictureMiniUrl));
txtUserName.Text = profile.GamerTag;
txtGamerScore.Text = int.Parse(profile.GamerScore).ToString("G 0,000");
txtZone.Text = profile.PlayerZone;
}
else
{
txtUserName.Text = "Failed to load data";
}
}
Now I understand I need to place something in ClientDownloadStringCompleted but I am unsure what.
The problem you have is that as soon as an asynchronous operation is introduced in to the code path the entire code path needs to become asynchronous.
Because GetResponse calls DownloadStringAsync it must become asynchronous, it can't return a string, it can only do that on a callback
Because GetGamer calls GetResponse which is now asynchronous it can't return a XboxGamer, it can only do that on a callback
Because GetGamerDetails calls GetGamer which is now asynchronous it can't continue with its code following the call, it can only do that after it has received a call back from GetGamer.
Because GetGamerDetails is now asynchronous anything call it must also acknowledge this behaviour.
.... this continues all the way up to the top of the chain where a user event will have occured.
Here is some air code that knocks some asynchronicity in to the code.
public static void GetGamer(string gamerTag, Action<XboxGamer?> completed)
{
var url = string.Format(BaseUrlFormat, gamerTag);
var response = GetResponse(url, null, null, (response) =>
{
completed(SerializeResponse(response));
});
}
internal static string GetResponse(string url, string userName, string password, Action<string> completed)
{
WebClient client = new WebClient();
if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password))
{
client.Credentials = new NetworkCredential(userName, password);
}
try
{
client.DownloadStringCompleted += (s, args) =>
{
// Messy error handling needed here, out of scope
completed(args.Result);
};
client.DownloadStringAsync(new Uri(url));
}
catch
{
completed(null);
}
}
public void GetGamerDetails()
{
var xboxManager = XboxFactory.GetXboxManager("DarkV1p3r");
xboxManager.GetGamer( (xboxGamer) =>
{
// Need to move to the main UI thread.
Dispatcher.BeginInvoke(new Action<XboxGamer?>(DisplayGamerDetails), xboxGamer);
});
}
void DisplayGamerDetails(XboxGamer? xboxGamer)
{
if (xboxGamer.HasValue)
{
var profile = xboxGamer.Value.Profile[0];
imgAvatar.Source = new BitmapImage(new Uri(profile.ProfilePictureMiniUrl));
txtUserName.Text = profile.GamerTag;
txtGamerScore.Text = int.Parse(profile.GamerScore).ToString("G 0,000");
txtZone.Text = profile.PlayerZone;
}
else
{
txtUserName.Text = "Failed to load data";
}
}
As you can see async programming can get realy messy.
You generally have 2 options. Either you expose your backend code as an async API as well, or you need to wait for the call to complete in GetResponse.
Doing it the async way would mean starting the process one place, then return, and have the UI update when data is available. This is generally the preferred way, since calling a blocking method on the UI thread will make your app seem unresponsive as long as the method is running.
I think the "Silverlight Way" would be to use databinding. Your XboxGamer object should implement the INotifyPropertyChanged interface. When you call GetGamer() it returns immediately with an "empty" XboxGamer object (maybe with GamerTag=="Loading..." or something). In your ClientDownloadStringCompleted handler you should deserialize the returned XML and then fire the INotifyPropertyChanged.PropertyChanged event.
If you look at the "Windows Phone Databound Application" project template in the SDK, the ItemViewModel class is implemented this way.
Here is how you can expose asynchronous features to any type on WP7.

Resources