Return special JsonResult in case of Exception occurs - asp.net-mvc

public JsonResult Menu() { // Exception }
I need application not to redirect user to the 404 page, but return special JSON result like { "result":1 }.
I wonder, is there any another solution, not try-catching.

You can implement your own FilterAttribute similar to the HandleErrorAttribute.
The HandleErrorAttribute normally does a redirect when an error occurs, but you could implement a similar attribute that returns a JsonResult. Something like the following will do:
public class CustomHandleErrorAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
filterContext.Result = new JsonResult
{
Data = new { result = 1 },
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
filterContext.ExceptionHandled = true;
}
}
And then
[CustomHandleError]
public JsonResult Menu()
{
throw new Exception();
}
I would recommend that you download the MVC source code from CodePlex and inspect the current implementation of HandleErrorAttribute. It is a lot more subtle than my crude implementation above and you may want some of its functionality.

Related

Error handling last chance to catch View Rendering exception

I've got ErrorController which customly handles my website errors.
It's pretty standard:
public class ErrorController : BaseController
{
public ActionResult Error404(Exception ex)
{
return View();
}
public ActionResult Error500(Exception ex)
{
return View();
}
}
However, in case if some rendering exception occurs inside of the View code (and this might occur, as the page has Master page (master layout) and different might happen), then I am not able to catch that rendering exception.
I can really see that exception with implementing ActionFilterAttribute.OnResultExecuted:
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
if (filterContext.Exception != null)
{
// not sure what to do here
} else base.OnResultExecuted(filterContext);
}
but in that case MVC looks for ~/Shared/Error.cshtml (incl. this path) after that exception occurs, and I can't provide the Errors view rendering exception to the user -- the "Last chance exception".
Is there any way to handle that?
Here is a nice article on Exception handling in ASP.Net MVC that should help
Method 4:- Inheriting from “HandleErrorAttribute”
public class CustomHandleErrorAttribute: HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
Exception ex = filterContext.Exception;
filterContext.ExceptionHandled = true;
var model = new HandleErrorInfo(filterContext.Exception, "Controller", "Action");
filterContext.Result = new ViewResult()
{
ViewName = "Error",
ViewData = new ViewDataDictionary(model)
};
}
}
And you attach that to your base controller.

Trigger authorization validation manually

I've a custom AuthorizeAttribute in my website. It has some logic about the Result created for unathorized requests.
In some cases, I want to trigger its validation manually*. I don't know if its possible. As I haven't found how to do that, I thought that I could extract the logic to get the Result to a diferrent method, and call it when I want. But then I don't know how to execute the ActionResult (outside de controllers).
How can I do to manually execute authorize validation? If not possible, how can I do to execute an ActionResult outside a controller?
*I need to trigger it manually because some request may pass the validation (because the session is created) and then, when accessing my services, found that the session was closed by someone else. I wouldn't like to add a call to the services in OnAuthorization to reduce services calls.
I'm not sure if its the best, but I've found a way to get it working (still listening for better answers).
When I call the services and notice that the work session has expired, all I do is removing the active user in the web session.
My custom authorize attribute also implements IResultFilter and IExceptionFilter.
In both OnResultExecuted and OnException I validate the active user once more. If the session was removed, then apply the same ActionResult that I would apply in OnAuthorization.
Here is the final class:
public class CustomAuthorizeAttribute : AuthorizeAttribute, IResultFilter, IExceptionFilter
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
ActionResult result = Validate(filterContext.HttpContext);
if (result != null)
filterContext.Result = result;
}
public void OnResultExecuted(ResultExecutedContext filterContext)
{
ActionResult result = Validate(filterContext.HttpContext);
if (result != null)
filterContext.Result = result;
}
public void OnResultExecuting(ResultExecutingContext filterContext)
{
}
public void OnException(ExceptionContext filterContext)
{
ActionResult result = Validate(filterContext.HttpContext);
if (result != null)
{
filterContext.Result = result;
filterContext.ExceptionHandled = true;
}
}
public static ActionResult Validate(HttpContextBase httpContext)
{
if (UserActiveInSession)
return null;
// Different rules to build an ActionResult for this specific case.
}
}
I did not get Diego answer's, But Just simply answering the title, I got it to work like that, You can use it as attribute on controllers actions and also trigger it manually at any place in C# or in Razor views.
namespace SomeNameSpace
{
public class CustomAuthorizeAttributeMVC : AuthorizeAttribute
{
private readonly string[] rolesParams;
public CustomAuthorizeAttributeMVC(params string[] roles)
{
this.rolesParams = roles;
}
public bool IsAuthorized { get {
//Do your authorization logic here and return true if the current user has permission/role for the passed "rolesParams"
string[] allowedRoles = new string[] {"role 1", "role 2", "role 3"};
return allowedRoles.Intersect(rolesParams).Any(); //for the example
}
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
return this.IsAuthorized;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
//...
}
}
public class AuthorizeHelper
{
public static bool HasPermission(params string[] roles)
{
return new CustomAuthorizeAttributeMVC(roles).IsAuthorized;
}
}
}
Usage example:
[CustomAuthorizeAttributeMVC("role 2")]
public ActionResult SomeAction()
{
return Content("Authorized !");
}
public ActionResult SomeOtherAction()
{
if(AuthorizeHelper.HasPermission("role 2"))
{
return Content("Authorized !");
}
return Content("401 Not Authorized !");
}
And as said, it can be used in Razor views by calling it normally
#if(AuthorizeHelper.HasPermission("role 2")) {
//...
}
Thanks

Reusable way to allow an account to be used by a single person at a time

I made a functionality that prevents multiple-login for one username at the same time and I call it in Actions like this:
int userId = (int)WebSecurity.CurrentUserId;
if ((this.Session.SessionID != dba.getSessionId(userId)) || dba.getSessionId(userId) == null)
{
WebSecurity.Logout();
return RedirectToAction("Index", "Home");
}
So the point is that every time user logins I save his sessionID into database field. So if someone with same username logins over someone already logged in with same username it overwrites that database field with this new session. If sessionID in DB is not the same as current session ID of logined user it log him out.
Is there a possibility to put this part of code in 1 place or do I have to put it in every single Action in my application?
I tried in Global.asax:
void Application_BeginRequest(object sender, EventArgs e)
{
if (Session["ID"] != null)
{
int userId = Convert.ToInt32(Session["ID"]);
if ((this.Session.SessionID != db.getSessionId(userId)) || db.getSessionId(userId) == null)
{
WebSecurity.Logout();
}
}
}
But I can't use Session here nor WebSecurity class if I try like this:
void Application_BeginRequest(object sender, EventArgs e)
{
int userId = (int)WebSecurity.CurrentUserId;
if ((this.Session.SessionID != db.getSessionId(userId)) || db.getSessionId(userId) == null)
{
WebSecurity.Logout();
Response.RedirectToRoute("Default");
}
}
because I get null reference exception.
EDIT
I used this:
void IActionFilter.OnActionExecuting(ActionExecutingContext filterContext)
{
int userId = (int)WebSecurity.CurrentUserId;
using (var db = new UsersContext())
{
string s = db.getSessionId(userId);
if ((filterContext.HttpContext.Session.SessionID != db.getSessionId(userId)) || db.getSessionId(userId) == null)
{
WebSecurity.Logout();
filterContext.Result = new RedirectResult("/Home/Index");
}
}
}
I had to use using statement for context, otherwise db.getSessionId(userId) was returning old sessionId. Method is this:
public string getSessionId(int userId)
{
string s = "";
var get = this.UserProfiles.Single(x => x.UserId == userId);
s = get.SessionId;
return s;
}
Very strange, will have to read about why that happened.
Everything works fine, except one thing. I have one JsonResult action in a controller, which returns Json, but since event(its textbox on enter event) can't trigger POST(I assume it's because it logs out before) redirect doesn't work. It can't even post to that Json action to receive callback and redirect. Any clues on that?
success: function (data) {
if (data.messageSaved) {
//data received - OK!
}
else {
// in case data was not received, something went wrong redirect out
window.location.href = urlhome;
}
}
Before I used ActionFilterAttribute I used code to check different sessions inside of POST and of course it could make callback and therefore redirect if didn't receive the data.. But now since it can't even POST and go into method it just stucks there and doesn't redirect :)
I would derive from AuthorizeAttribute. No need to check this information if you don't need to authorize the request.
public class SingleLoginAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
bool isAuthorized = base.AuthorizeCore(httpContext);
if (isAuthorized)
{
int userId = (int)WebSecurity.CurrentUserId;
if ((filterContext.HttpContext.Session.SessionID != dba.getSessionId(userId))
|| dba.getSessionId(userId) == null)
{
WebSecurity.Logout();
isAuthorized = false;
filterContext.Result = new RedirectResult("/Home/Index");
}
}
return isAuthorized;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.Result = new JsonResult()
{
Data = FormsAuthentication.LoginUrl,
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
else
{
base.HandleUnauthorizedRequest(filterContext);
}
}
}
I'd also mention that this allows you to short circuit other ActionFilters because they run after OnAuthorization.
Forward Order - OnAuthorization : AuthorizationFilter (Scope Controller)
Forward Order - OnActionExecuting : ActionFilter1 (Scope Global)
Forward Order - OnActionExecuting : ActionFilter2 (Scope Controller)
Forward Order - OnActionExecuting : ActionFilter3 (Scope Action)
Then as Rob Lyndon mentioned, you could in the FilterConfig (MVC4)
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new SingleLoginAuthorizeAttribute());
}
}
Then when you don't want to require any authorization, you can use the AllowAnonymouseAttribute on your ActionResult methods or Controller Class to allow anonymous access.
Update
I added a way for your ajax calls (Get or Post) to work with timeouts. You can do something like:
success: function (jsonResult)
{
if (jsonResult.indexOf('http') == 0)
{
window.location = jsonResult;
}
// do other stuff with the Ajax Result
}
This isn't exactly the best way, but if you want more information on how to do this better I would ask another question instead of appending more questions on this one.
The ActionFilterAttribute is the way to go.
We created an Action Filter called SeatCheck and decorate each controller like this:
[SeatCheck]
public class NoteController : BaseController
{
We use that to get a count of seats and other functions, but it makes it so much easier to control everywhere without thinking about it.
In the proejct ActionFilters folder we have the SeatCheck.cs file that looks like this:
namespace site.ActionFilters
{
public class SeatCheckAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
You can get the SessionID in the Action Filter like this
filterContext.HttpContext.Session.SessionID
Create a custom action filter, and put that code in the filter, then apply the filter to your controller.
Yes, indeed there is. You can use an attribute derived from ActionFilterAttribute.
I would write a class called SessionSecurityAttribute:
public class SessionSecurityAttribute : ActionFilterAttribute
{
public MyDbConn MyDbConn { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var session = filterContext.RequestContext.HttpContext.Session;
if (session["ID"] != null && WebSecurity.IsAuthenticated)
{
int userId = Convert.ToInt32(session["ID"]);
if ((sessionID != MyDbConn.getSessionId(userId)) || MyDbConn.getSessionId(userId) == null)
{
WebSecurity.Logout();
}
}
}
}
The question remains: how can you add these attributes to your actions whilst giving them access to your database? That's easy: in Global.asax you can call into the bootstrapping RegisterGlobalFilters method:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new SessionSecurityAttribute
{
MyDbConn = DependencyResolver.Current.GetService<MyDbConn>()
});
}
This adds your SessionSecurityAttribute, complete with DB connection, to every action by default, without a line of repeated code.
You might try implementing your own custom ISessionIDManager:
http://msdn.microsoft.com/en-us/library/system.web.sessionstate.isessionidmanager.aspx
In the validate, check to see if it's still valid, otherwise return false.

Automatic C# MVC Error Handling

I've got this code.
public ActionResult Index()
{
ReceiptModel model = new ReceiptModel();
try
{
model = new ReceiptModel(context);
}
catch (BussinessException bex)
{
ModelState.AddModelError("Index", bex.MessageToDisplay);
return View("Index");
}
return View(model);
}
BussinesException ir returned from database and then displayed for user. I have to put on every controller method try-catch statement, which is a bit tedious. Is there any easier way how to handle these exceptions?
P.S. All other exceptions are handled with HandleExceptionAttribute
UPDATE:
I used Floradu88 approach. So Now i have something like this.
public sealed class HandleBussinessExceptionAttribute : HandleErrorAttribute, IExceptionFilter
{
public override void OnException(ExceptionContext filterContext)
{
filterContext.Controller.TempData["UnhandledException"] = filterContext.Exception;
filterContext.ExceptionHandled = true;
((Controller)filterContext.Controller).ModelState.AddModelError(
((BussinessException)filterContext.Exception).Code.ToString(),
((BussinessException)filterContext.Exception).MessageToDisplay
);
filterContext.Result = new ViewResult
{
ViewName = this.View,
TempData = filterContext.Controller.TempData,
ViewData = filterContext.Controller.ViewData,
};
}
}
and on Controller action i put
[HandleBussinessExceptionAttribute(Order = 2, ExceptionType = typeof(BussinessException), View = "Login")]
i also tried in exception handler:
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(filterContext.RouteData));
and then handle error in action with ModelState.IsValid but values to action comes null. So for now i use first approach. When i have a bit more time i'll try to fix second approach.
Please read the documentation on this part:
http://msdn.microsoft.com/en-us/library/gg416513%28v=vs.98%29.aspx
http://www.asp.net/web-api/overview/web-api-routing-and-actions/exception-handling
http://www.asp.net/mvc/tutorials/older-versions/controllers-and-routing/understanding-action-filters-cs
Too much content to be posted here:
public class NotImplExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
if (context.Exception is NotImplementedException)
{
context.Response = new HttpResponseMessage(HttpStatusCode.NotImplemented);
}
}
}
And your controller:
public class ProductsController : ApiController
{
[NotImplExceptionFilter]
public Contact GetContact(int id)
{
throw new NotImplementedException("This method is not implemented");
}
}
According to this post, Create a custom binder and put the model in TempData:
public class AppBinder : DefaultModelBinder
{
protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
base.OnModelUpdated(controllerContext, bindingContext);
controllerContext.Controller.TempData["model"] = bindingContext.Model;
}
}
Register it in global.asax:
ModelBinders.Binders.DefaultBinder = new AppBinder();
Create a BaseController and override the OnException:
protected override void OnException(ExceptionContext filterContext)
{
filterContext.ExceptionHandled = true;
this.ModelState.AddModelError(string.Empty, filterContext.Exception.Message);
filterContext.Result = new ViewResult
{
ViewName = filterContext.RouteData.Values["action"].ToString(),
TempData = filterContext.Controller.TempData,
ViewData = filterContext.Controller.ViewData
};
base.OnException(filterContext);
}
All Actions int The Controllers which inherited from this base controller will show their unhandled exceptions their own view in validation summery (remember to have
#Html.ValidationSummary(true)
in page). it works for me, hope works for you too!

MVC4 jQueryMobile won't show custom error page OnException

I'm trying to get a custom error page to display from a MVC4 Mobile Application but keep just getting the "Error Loading Page" yellow message being displayed instead of my custom page.
I have tried using the HandleErrorAttribute as below on Actions and Controllers with no success
[HandleError(ExceptionType = typeof(SqlException), View = "DatabaseError")]
I have also tried overriding the OnException method of my base controller but this also doesn't appear to have any effect.
protected override void OnException(ExceptionContext filterContext)
{
if (filterContext == null)
base.OnException(filterContext);
Logger.LogException(filterContext.Exception);
if (filterContext.Exception is SqlException)
{
filterContext.Result = new ViewResult { ViewName = "DatabaseError" };
}
if (filterContext.Exception is SomeOtherException)
{
filterContext.Result = new ViewResult { ViewName = "Error" };
}
if (filterContext.HttpContext.IsCustomErrorEnabled)
{
filterContext.ExceptionHandled = true;
filterContext.Result.ExecuteResult(this.ControllerContext);
}
}
If I try these methods on a non jQueryMobile MVC4 application they work as expected, just not in my mobile application!
Anyone have any insight as to why and how to make this work??
Ok so by disabling Ajax the appropriate error pages now get displayed!
In my _layout.cshtml page I added the following javascript:
$.mobile.ajaxEnabled = false;
You probably need to check in your filter if the request is via AJAX and return a JsonResult instead of a ViewResult, something like:
public class TypeSwitchingHandleErrorAttribute : HandleErrorAttribute
{
private static readonly string[] AJAX_ACCEPT_TYPES = new[] { "application/json", "application/javascript", "application/xml" };
private bool IsAjax(ExceptionContext filterContext)
{
return filterContext.HttpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest"
||
filterContext.HttpContext.Request.AcceptTypes.ContainsAny(AJAX_ACCEPT_TYPES);
}
private void setResult(ExceptionContext filterContext, object content)
{
if( IsAjax(filterContext) )
{
filterContext.Result = new JsonResult { Data = content, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
} else
{
filterContext.Result = new ViewResult { ViewName = (string)content };
}
}
public override void OnException(ExceptionContext filterContext)
{
// your code...then where you set the result...
setResult(filterContext, "DatabaseError etc");
}
}
Then you'd have to interpret the ajax response appropriately on client-side. You could also send different content if it's an ajax request, like a standard {success: t/f, message: Exception.Message } object, and set the response status codes appropriately as well.

Resources