ASP.NET MVC AJAX onError handling - asp.net-mvc

I am trying to delete item from table. There is Ajax link for it.
#Ajax.ActionLink("Delete", "DeleteConfirm", new { id = Model.ID }, new AjaxOptions {
HttpMethod = "POST", UpdateTargetId = "TableID", OnSuccess = "CloseDialog", OnFailure = "AlerDialog"
})
It calls DeleteConfirm method from controller with POST method. I made simple controller which should do something so ActionLink should catch error and run OnFailure function (to show alert dialog).
Controller:
public ActionResult DeleteConfirm(int id)
{
// code here
}
What to return from controller method so OnFailure function invokes?

OnError is fired when error happens on serverside. By error, I mean exception, and I think you can't pass exception message on clientside except of 500 Server error.
I think that good aproach is to have some CustomResponse class that your action will return.
In your case, something like:
Class DeletionResponse
{
public bool IsDeletionSuccesfull {get; set; }
public string Message {get; set;}
}
In DeleteConfirm action you create new response, which maybe needs to inheriteActionResult class(I'm not sure because I'm new to MVC). If some error ocures while deleting, set DeletionSuccesfull to false, and Message to message of exception, or some custom message.
On client side, the point is to examine success in OnSuccess handler, and then decide what to do.
Something like:
function handleResponse(deletionResponse){
if(deletionResponse.d.IsDeletionSuccesfull){
CloseDialog();
}
else{
AlertDialog(deletionResponse.d.Message);
}
}

The OnFailure will fire based off the status code of the result, so something like this would give the desired effect.
return new HttpStatusCodeResult(HttpStatusCode.InternalServerError, "Reason for failure");
Also, not sure if it's related but shouldn't your OnFailure text be "AlertDialog" instead of "AlerDialog"?
EDIT: In your controller action you should be able to test that the request is being made via Ajax by using this Extension method MVC provides Request.IsAjaxRequest(). Note that there is no true way to check if a request is an Ajax request on the server, this method is utilizing the presence of a custom header jQuery sets for all ajax requests it makes, in other words don't use Request.IsAjaxRequest() in business logic.
Source for IsAjaxRequest() method
namespace System.Web.Mvc
{
public static class AjaxRequestExtensions
{
public static bool IsAjaxRequest(this HttpRequestBase request)
{
if (request == null)
{
throw new ArgumentNullException("request");
}
return (request["X-Requested-With"] == "XMLHttpRequest") || ((request.Headers != null) && (request.Headers["X-Requested-With"] == "XMLHttpRequest"));
}
}
}

how about throwing an exception?
public ActionResult DeleteConfirm(int id)
{
try
{
//make your call to the database here
return View();
}
catch (ExceptionType1 ex)
{
//log the details of your error for support purposes, alerting tracing etc.
ex.Message = "Nice message for user"
throw ex;
}
catch (ExceptionType2 ex)
{
//log the details of your error for support purposes, alerting tracing etc.
ex.Message = "Another nice message for user"
throw ex;
}
}
Your ajax call would then know it was a failure, and run the correct function.

Related

What is the best way to test HTTPPOST request by passing model?

I am using Castle Windsor as DI and using reposity to access and implement the data layers.
As I have implemented all the data access layer in my repo, it is time to call those methods in my API controller. SO I have 'getAllReportsByClient method' and 'CreateReport' POST method. So, in order to test if some methods are working without actually implementing the view and AJAX call, how can I insert sample data using my 'Create Report' method?
The method from repo is below:
public void CreateReport(TReportHeaderModel model)
{
using (var connection = new TReportEntitiesConnection())
{
connection.THeader.Add(new THeader()
{
ClientID=model.ClientID,
ID=model.ID,
THeaderTitle=model.THeaderTitle,
RowNumber=model.RowNumber
});
foreach (var d in model.TReports)
{
connection.TReport.Add(new TReport()
{
ID=d.ID,
TReportName=d.TReportName,
URL=d.URL,
RowNumber=d.RowNumber,
});
}
connection.SaveChanges();
}
throw new NotImplementedException();
}
The below is HTTPPOST createReport call in Controller:
[HttpPost]
public ActionResultModel CreateReport([FromBody] TReportHeaderModel model)
{
try
{
_tReportingService.CreateReport(model);
return new ActionResultModel() //return void, must not be followed by object expression
{
Success = true,
Message = "Report Successfully Created."
};
}
catch (Exception ex)
{
return new ActionResultModel()
{
Success = false,
Message = "Report not created.",
Obj=ex.Message
};
}
}
You could use Postman(https://www.getpostman.com/) or Fiddler(www.telerik.com/fiddler) to emulate the request. Even better, you could write a test using a testing framework.

How to show error message to user in MVC calling Web API?

I am working on a MVC Web App which is calling a Web API. In my Create (POST) method, a user will enter email addresses of some users in the database. I have a check to enter the email only if the email does not already exist in the database or not. If it already exists, I want to be able to show an error message to the user "Email already exists".
I thought HttpStatusCode.NotFound, "Email exists already." would work, but I think it will only show in PostMan. How can I show it on the MVC side with like ViewBag.ErrorMessage?
API
public IHttpActionResult PostApprovedUsers(ApprovedUsers approvedUsers)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (!db.ApprovedUsers.Any(u => u.Email == approvedUsers.Email))
{
db.ApprovedUsers.Add(approvedUsers);
db.SaveChanges();
}
else
{
return Content(HttpStatusCode.NotFound, "Email exists already.");
}
return CreatedAtRoute("DefaultApi", new { id = approvedUsers.Email }, approvedUsers);
MVC Create
public ActionResult Create([Bind(Include = "Email,FirstName,LastName")] ApprovedUsers approvedUsers)
{
using (WebClient client = new WebClient())
{
token = Session.GetDataFromSession<string>("access_token");
client.Headers.Add("authorization", "Bearer " + token);
client.UploadValues(apiUrl, "POST", new NameValueCollection()
{
{ "Email", approvedUsers.Email },
{ "FirstName",approvedUsers.FirstName },
{ "LastName",approvedUsers.LastName }
});
}
return RedirectToAction("Index");
}
The main problem with what you were seeing is that you were not in fact doing anything with the return value of webclient. That is ok though, since based on our discussion everything is in the same project and that would not be how you would want to do this using ASP.NET MVC and Web API.
Since these are all in the same project, we do want to combine common functionality but not within a controller. The DB service should be abstracted away into a seperate project - or class - and then injected into both controllers using some form of IoC Container. There are a couple of options available and I personally usually use Ninject for my .NET projects.
Assuming you have that functionality your MVC Controller and API controller should then both program against that abstraction. Interface based programming is how .NET works best.
With that in mind your new API controller would look something like this:
public IHttpActionResult PostApprovedUsers([Bind(Include="Email, FirstName, LastName")]ApprovedUser approvedUser)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (_db.UserExists(approvedUser))
{
return Content(HttpStatusCode.NotFound, "Email exists Already");
}
_db.AddUser(approvedUser);
return CreatedAtRoute("DefaultApi", new {id = approvedUser.Email}, approvedUser);
}
Where _db is the service that gets injected into the controller. Please not how common functionality such as checking to see if a user exists and adding a user are wrapped behind the abstraction:
public interface IMyFakeDbService
{
IQueryable<ApprovedUser> ApprovedUsers { get; }
int RemoveUser(ApprovedUser user);
int RemoveUser(string email);
bool UserExists(ApprovedUser user);
void AddUser(ApprovedUser user);
}
This then brings us to your MVC Controller. The same principle applies; since all the common functionality is in the db service just inject that into the controller instead of calling the API controller. You will see that you can use ModelState.AddModelError here to pass information back into the view.
public ActionResult Create()
{
return View();
}
[HttpPost]
public ActionResult Create([Bind(Include = "Email, FirstName, LastName")]ApprovedUser user)
{
if (ModelState.IsValid)
{
if (!_db.UserExists(user))
{
_db.AddUser(user);
return RedirectToAction("Index");
}
ModelState.AddModelError("","A user with that email already exists");
}
return View();
}
Finally
I made a public git repo with a complete project here
The DB calls should be async. You are wasting server resources if not
If you want to learn MVC real quick I would suggest Pro ASP.NET MVC 5 and Pro ASP.NET Web API2
I always use Ok<T>, where T is a model I defined which have 3 variables, one will identify the status (true, false) and one will be a string message, and one will have data type of List<object> which I need to receive at end point, then you can modify the error message as you want
in your case instead of this
return BadRequest(ModelState);
I will replace it with something like this
return Ok<MyResponse>(new MyResponse{ Message = "Error Message Here", Status = false});
then once you get your content you can desirialize it into your object
MyResponse response = new MyResponse();
HttpResponseMessage response = await client.GetAsync(path);
// ok will return success status code, but you will depend on your status to know if it was success or error happened
if (response.IsSuccessStatusCode)
{
// Desirialize your result
response = await response.Content.ReadAsAsync<MyResponse>();
}
// then get the message,
if(response.Status)
{
//it was fine, return something ..
}
else {
// Error happened, get your error message and disaply it, if you want as viewbag
ViewBag.ErrorMessage = response.Message;
// do something
}

Return Error or message of a view on another view in mvc

There is a Login modal on _layout.cshtml page , so that user can login at any time or at any page. When the modal load ,we are appending a 'Login' view to the modal by $("#divModal").load("/Login/Index");
while submitting the Form , Everything works fine.
But i was just wondering if there is any way to display the error or custom message like "Login failed" on the dialogBox on the same page except using Jquery ajax.
1) I tried to return view() but then it is redirecting the page to Login/Index page.
2) we can't use partial view then we have to pass LOgin Model with every other models.
Besides using Jquery ajax , is there any other way to achieve this.
You can use JsonResult if the error occurs. So you could write a custom action filter on the server which catches exception and transforms them into JSON response:
public class MyErrorHandlerAttribute : FilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
filterContext.ExceptionHandled = true;
filterContext.Result = new JsonResult
{
Data = new { success = false, error = filterContext.Exception.ToString() },
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
}
Then in your view you can add call back in your ajax call:
error: function(xhr) {
try {
// a try/catch is recommended as the error handler
// could occur in many events and there might not be
// a JSON response from the server
var json = $.parseJSON(xhr.responseText);
alert(json.errorMessage);
} catch(e) {
alert('something bad happened');
}
}

How to recieve and display JSON erroneous response in case of asp.net mvc Ajax in MVC 3.0 App

I am using CustomErrorHandler attribute for handling errors in my asp.net mvc application. The code is as follows:
public class CustomHandleErrorAttribute : HandleErrorAttribute // Error handler
{
public override void OnException(ExceptionContext filterContext)
{
if (filterContext.ExceptionHandled || !filterContext.HttpContext.IsCustomErrorEnabled)
{
return;
}
if (new HttpException(null, filterContext.Exception).GetHttpCode() != 500)
{
return;
}
if (!ExceptionType.IsInstanceOfType(filterContext.Exception))
{
return;
}
// if the request is AJAX return JSON else view.
if (filterContext.HttpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest")
{
filterContext.Result = AjaxError(filterContext.Exception.Message, filterContext);
}
else
{
filterContext.ExceptionHandled = true;
var controllerName = (string)filterContext.RouteData.Values["controller"];
var actionName = (string)filterContext.RouteData.Values["action"];
var model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);
filterContext.Result = new ViewResult
{
ViewName = View,
MasterName = Master,
ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
TempData = filterContext.Controller.TempData
};
}
}
protected JsonResult AjaxError(string message, ExceptionContext filterContext)
{
if (String.IsNullOrEmpty(message))
message = "Something went wrong while processing your request. Please refresh the page and try again.";
filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
return new JsonResult { Data = new { ErrorMessage = message }, ContentEncoding = System.Text.Encoding.UTF8, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}
}
}
It has the capability to catch both the normal errors and Ajax errors.
In case of normal errors, i am building an error model and displaying the error view
But, In case of Ajax errors, i want to show the error message in the form of JSON.
I tried some thing like:
function Failed(data) { // Onfailed call of MVC Ajax form
alert("Sorry,An error occured while processing your request");
alert(data.ErrorMessage);
}
But, it is saying undefined for data.ErrorMessage instead of showing actual error message.
Please help/suggest on how to handle this.
Updated2: This is how it got solved:
function Failed(jqXHR, textStatus, errorThrown) {
var Error = $.parseJSON(jqXHR.responseText);
alert("Sorry,An error occured while processing your request");
alert(Error.ErrorMessage);
}
I find strange that you want display some HTML form in case of Ajax error. Ajax request uses typically .fail() method or error callback of jQuery.ajax to get error information. Inside of the callback one can decode the server response using var error = $.parseJSON(jqXHR.responseText); and then display some error message like alert(error.ErrorMessage); or more complex dialog. I suppose that main problem in your current code is because you don't set filterContext.ExceptionHandled = true; in case of Ajax request in your code.
Small remark to your current code: I think that you can safe use IsAjaxRequest extension (in the form if (filterContext.HttpContext.Request.IsAjaxRequest()) {...}) instead of testing .Headers["X-Requested-With"].
It looks like you're not forming the return statement properly. In your JsonResult action, try:
var returnObj = new { ErrorMessage = message };
return Json(returnObj, JsonRequestBehavior = JsonRequestBehavior.AllowGet);
If this doesn't solve the problem:
Do a console.log on the AJAX response object in your JavaScript.
Make sure that you are accessing the correct property name.
Make sure that JavaScript is recognizing your return as a valid object. If it is still in string form, you may need to do:
var returnedObj = JSON.parse(jsonString);
Example
A console.log of the response from one of my webAPI controllers was producing this:
The JSON object is in string form in one of the response properties. I got the object i wanted by doing:
var returnedObj = JSON.parse(response.responseText);
A console.log of the returnedObj produced:

asp.net mvc custom exception filter to force a return of full view, not partial

I have a custom exception filter that I'm calling by virtue of adding a [CustomExceptionFilter] attribute to my class. It works as I'd like it to, however if the action method is returning a partial view (through an ajax request), the exception (which is basically a redirect to a not authorized page), is loading up the partial view with that page. Is there a way I can force it to reload the 'parent' url?
Here is the code for the custom exception filter
public class CustomExceptionFilter : FilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
if (filterContext.Exception.GetType() == typeof(CustomSecurityException))
{
filterContext.ExceptionHandled = true;
RequestContext rc = new RequestContext(filterContext.HttpContext, filterContext.RouteData);
string url = RouteTable.Routes.GetVirtualPath(rc, new RouteValueDictionary(new { Controller = "NoAccess", action = "Index", message = filterContext.Exception.Message })).VirtualPath;
filterContext.HttpContext.Response.Redirect(url, true);
}
}
}
This is something you need to handle on the browser. Try handling the error() on jQuery.ajax() call for example (and obviously don't return redirect..).
I would suggest letting the exception bubble up to the client and handle it like Maxwell suggested.
In our previous project we used a specific actionfilter for handling ajax errors (borrowed from Suteki Shop). Note that the response status is 500 (internal server error). An error status is required for the response in order to call de Error() delegate within a JQuery.ajax() call.
public class HandleErrorWithAjaxAttribute : HandleErrorAttribute
{
public HandleErrorWithAjaxAttribute()
{
ShowStackTraceIfNotDebug = true;
}
public bool ShowStackTraceIfNotDebug { get; set; }
public override void OnException(ExceptionContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
string content = ShowStackTraceIfNotDebug ||
filterContext.HttpContext.IsDebuggingEnabled
?
filterContext.Exception.StackTrace
:
string.Empty;
filterContext.Result = new ContentResult
{
ContentType = MediaTypeNames.Text.Plain,
Content = content
};
filterContext.HttpContext.Response.Status =
"500 " + filterContext.Exception.Message
.Replace("\r", " ")
.Replace("\n", " ");
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
}
else
{
base.OnException(filterContext);
}
}
}
I use the OnFailure hanlder in form tag.
<form id="AJAXForm" method="post" action=""
onsubmit="Sys.Mvc.AsyncForm.handleSubmit(this, new Sys.UI.DomEvent(event),
{ insertionMode: Sys.Mvc.InsertionMode.replace, httpMethod: 'POST',
updateTargetId: 'myPartialPage', onSuccess: Function.createDelegate(this, ajaxFormSucced),
onFailure: Function.createDelegate(this, ajaxFormFailure) });" >
...
function ajaxFormSucced(){
// Code for success
}
function ajaxFormFailure(){
// Code for failure
}
You can verify if the request is an ajax request or not.
You could for example do the following...
if (!filterContext.HttpContext.Request.IsAjaxRequest()){
//Return a ViewResult
//filterContext.ExceptionHandled = true;
//filterContext.Result = new ViewResult { ViewName = "Error" ... };
}
else{
//An ajax request.
//return a partial view
}
However, as Maxwell said you could let the exeption bubble up if it is an ajax request and handle the error on the client. You can setup globally a way of handling exceptions in ajax requests like it is described here
Did you try clearing the response? The controller may still be setting response content.
filterContext.HttpContext.Response.Clear()
filterContext.Result = new JsonResult { Data = new { Message = message } };
filterContext.HttpContext.Response.StatusCode = 500;
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
This link helped me
Handling ASP.NET MVC exceptions when posting to the controller via Ajax using jQuery
Lastly, When testing the javascript function, start with alert on the first line. Any javascript errors in your function are likely to stop the function in mid execution without javascript error feedback via the browser (depending on your setup).
This will help you.
Just add .IsAjaxRequest extension method and return 403 status code to browser, jquery ajaxError will handle it redirecting to login page
As Maxwell says, handle on the client, using something like this
function handleError(ajaxContext) {
// Load parent
}
#using (Ajax.BeginForm("Index", "Home", new AjaxOptions
{
UpdateTargetId = "MyDiv",
OnFailure = "handleError"
}))
What you must do though is make sure in the controller ActionResult code for NoAccess contains the following code, so that your ajax error is triggered.
HttpContext.Response.StatusCode = 401;

Resources