ASP.NET MVC3 Stop executing action/controller in custom AuthorizeAttribute - asp.net-mvc

How I can stop executing action/controller without redirection, and only return Response with statusCode
public class MainAuthorizationFilter : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
... [my Authorization login] ...
if([Authorization fail])
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.HttpContext.Response.StatusCode = 401;
// HERE I want stop executing action/controller because I want return only statusCode
}
else
{
// In non-ajax request I just redirect me request and action/contoller isn't executed
filterContext.Result = new RedirectToRouteResult("Error", new RouteValueDictionary { { "errorCode", errorCode } });
}
}
}
base.OnAuthorization(filterContext);
}
[MainAuthorizationFilter]
public ActionResult CreateFolder(...)
{
CreateFolder(...);
}

filterContext.Result = new HttpStatusCodeResult(401, "String description here if you want");
HttpStatusCodeResult on MSDN
Note that the forms auth module may intercept this and convert it to a redirect to your login page - not sure if this applies to AJAX requests too, I haven't tried it...

I would simply end the response like so:
public class MainAuthorizationFilter : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
... [my Authorization login] ...
if([Authorization fail])
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.HttpContext.Response.StatusCode = 403;
filterContext.HttpContext.Response.End();
}
else
{
// In non-ajax request I just redirect me request and action/contoller isn't executed
filterContext.Result = new RedirectToRouteResult("Error", new RouteValueDictionary { { "errorCode", errorCode } });
}
}
}
base.OnAuthorization(filterContext);
}

Related

In ASP.NET MVC how to refactor try/catch and return JSON error message?

How best to create a base controller in a new ASP.NET MVC application that will contain boilerplate code to handle all try/catch routines for every action in derived controllers and return a standard JSON error message after logging to Nlog. I would also like to handle 404 errors to redirect to a custom 404 view. I aim using OWIN cookie authentication and would like to signout when the cookie expires. All my actions will be returning JsonResult and invoked via jQuery Ajax.
In previous projects I've used the following approach:
In Global.asax.cs
protected void Application_Error(object sender, EventArgs e)
{
Exception lastError = Server.GetLastError();
Server.ClearError();
var statusCode = 0;
statusCode = lastError.GetType() == typeof(HttpException) ? ((HttpException)lastError).GetHttpCode() : 500;
var routeData = new RouteData();
routeData.Values.Add("controller", "Error");
routeData.Values.Add("statusCode", statusCode);
routeData.Values.Add("exception", lastError);
if (new HttpRequestWrapper(HttpContext.Current.Request).IsAjaxRequest())
{
routeData.Values.Add("action", "Ajax");
}
else
{
routeData.Values.Add("action", "Index");
}
var requestContext = new RequestContext(new HttpContextWrapper(Context), routeData);
IController controller = new ErrorController();
controller.Execute(requestContext);
Response.End();
}
In ErrorController.cs
public class ErrorController : Controller
{
public ActionResult Index(int statusCode, Exception exception)
{
var model = new ErrorModel { HttpStatusCode = statusCode, Exception = exception.Message };
Response.StatusCode = statusCode;
return View(model);
}
public JsonResult Ajax(int statusCode, Exception exception, Dictionary<string,string> validationErrors = null)
{
var model = new ErrorModel { HttpStatusCode = statusCode, Exception = exception.Message };
if (exception.GetType() == typeof (DbEntityValidationException))
{
model.ValidationErrors = null;
}
else
{
model.ValidationErrors = validationErrors;
}
Response.StatusCode = statusCode;
return Json(model, JsonRequestBehavior.AllowGet);
}
}
The best way of doing that is by extending the HandleErrorAttribute like this:
public class AjaxAwareHandleErrorAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
// Execute the normal exception handling routine...
base.OnException(filterContext);
// Verify if AJAX request...
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
// Use Json in case of AJAX request...
var result = new JsonResult();
result.Data = new { Error = filterContext.Exception.Message };
filterContext.Result = result;
}
}
}
Then, all you need to do is to register that attribute in your FilterConfig class under the App_Start folder like this:
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new AjaxAwareHandleErrorAttribute());
}
}

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.

How can I use an action filter in ASP.NET MVC to route to a different view but using the same URL?

Is it possible to make a filter that, after a controller action has been (mostly) processed, checks for a certain test condition and routes to a different view transparently to the user (i.e., no change in the URL)?
Here would be my best guess at some pseudocode:
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
// If some condition is true
// Change the resulting view resolution to XYZ
base.OnResultExecuting(filterContext);
}
filterContext.Result = new ViewResult
{
ViewName = "~/Views/SomeController/SomeView.cshtml"
};
This will short-circuit the execution of the action.
also you can return view as from your action
public ActionResult Index()
{
return View(#"~/Views/SomeView.aspx");
}
This is what I ended up doing, and wrapped up into a reusable attribute and the great thing is it retains the original URL while redirecting (or applying whatever result you wish) based on your requirements:
public class AuthoriseSiteAccessAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
// Perform your condition, or straight result assignment here.
// For me I had to test the existance of a cookie.
if (yourConditionHere)
filterContext.Result = new SiteAccessDeniedResult();
}
}
public class SiteAccessDeniedResult : ViewResult
{
public SiteAccessDeniedResult()
{
ViewName = "~/Views/SiteAccess/Login.cshtml";
}
}
Then just add the attribute [SiteAccessAuthorise] to your controllers you wish to apply the authorisation access to (in my case) or add it to a BaseController. Make sure though the action you are redirecting to's underlying controller does not have the attribute though, or you'll be caught in an endless loop!
I have extended the AuthorizeAttribute of ASP.NET MVC action filter as DCIMAuthorize, in which I perform some security checks and if user is not authenticated or authorized then action filter will take user to access denied page. My implementation is as below:
public class DCIMAuthorize : AuthorizeAttribute
{
public string BusinessComponent { get; set; }
public string Action { get; set; }
public bool ResturnJsonResponse { get; set; }
public bool Authorize { get; set; }
public DCIMAuthorize()
{
ResturnJsonResponse = true;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
try
{
//to check whether user is authenticated
if (!httpContext.User.Identity.IsAuthenticated)
return false;
//to check site level access
if (HttpContext.Current.Session["UserSites"] != null)
{
var allSites = (VList<VSiteList>)HttpContext.Current.Session["UserSites"];
if (allSites.Count <= 0)
return false;
}
else
return false;
// use Authorize for authorization
Authorize = false;
string[] roles = null;
//get roles for currently login user
if (HttpContext.Current.Session["Roles"] != null)
{
roles = (string[])HttpContext.Current.Session["Roles"];
}
if (roles != null)
{
//for multiple roles
string[] keys = new string[roles.Length];
int index = 0;
// for each role, there is separate key
foreach (string role in roles)
{
keys[index] = role + "-" + BusinessComponent + "-" + Action;
index++;
}
//access Authorization Details and compare with keys
if (HttpContext.Current.Application["AuthorizationDetails"] != null)
{
Hashtable authorizationDetails = (Hashtable)HttpContext.Current.Application["AuthorizationDetails"];
bool hasKey = false;
foreach (var item in keys)
{
hasKey = authorizationDetails.ContainsKey(item);
if (hasKey)
{
Authorize = hasKey;
break;
}
}
}
}
return base.AuthorizeCore(httpContext);
}
catch (Exception)
{
throw;
}
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
try
{
filterContext.Controller.ViewData["ResturnJsonResponse"] = ResturnJsonResponse;
base.OnAuthorization(filterContext);
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
// auth failed, redirect to login page
filterContext.Result = new HttpUnauthorizedResult();
return;
}
if (!Authorize)
{
//Authorization failed, redirect to Access Denied Page
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary{{ "controller", "Base" },
{ "action", "AccessDenied" }
//{ "returnUrl", filterContext.HttpContext.Request.RawUrl }
});
}
}
catch (Exception)
{
throw;
}
}
}
You Can Also Save All Route File Path in a Static And Use it Like This :
public static class ViewPath
{
public const string SomeViewName = "~/Views/SomeViewName.cshtml";
//...
}
And into Your ActionFilter :
context.Result = new ViewResult()
{
ViewName = ViewPath.SomeViewName /*"~/Views/SomeViewName.cshtml"*/
};

Handling session timeout in ajax calls

I'm making an ajax call using jquery to an asp.net mvc controller action:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult GetWeek(string startDay)
{
var daysOfWeek = CompanyUtility.GetWeek(User.Company.Id, startDay);
return Json(daysOfWeek);
}
When session times out, this call will fail, as the User object is stored in session. I created a custom authorize attribute in order to check if session was lost and redirect to the login page. This works fine for page requests, however it doesn't work for ajax requests, as you can't redirect from an ajax request:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorizeUserAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (!httpContext.Request.IsAjaxRequest())
{//validate http request.
if (!httpContext.Request.IsAuthenticated
|| httpContext.Session["User"] == null)
{
FormsAuthentication.SignOut();
httpContext.Response.Redirect("~/?returnurl=" + httpContext.Request.Url.ToString());
return false;
}
}
return true;
}
}
I read on another thread that when the user isn't authenticated and you make an ajax request, you should set the status code to 401 (unauthorized) and then check for that in js and redirect them to the login page. However, I can't get this working:
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (Request.IsAjaxRequest() && (!Request.IsAuthenticated || User == null))
{
filterContext.RequestContext.HttpContext.Response.StatusCode = 401;
}
else
{
base.OnActionExecuting(filterContext);
}
}
Basically, it'll set it to 401, but then it'll continue into the controller action and throw an object ref not set to an instance of an object error, which then returns error 500 back to the client-side js. If I change my custom Authorize attribute to validate ajax requests as well and return false for those that aren't authenticated, that makes the ajax request return my login page, which obviously doesn't work.
How do I get this working?
You could write a custom [Authorize] attribute which would return JSON instead of throwing a 401 exception in case of unauthorized access which would allow client scripts to handle the scenario gracefully:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.Result = new JsonResult
{
Data = new
{
// put whatever data you want which will be sent
// to the client
message = "sorry, but you were logged out"
},
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
else
{
base.HandleUnauthorizedRequest(filterContext);
}
}
}
then decorate your controller/actions with it and on the client:
$.get('#Url.Action("SomeAction")', function (result) {
if (result.message) {
alert(result.message);
} else {
// do whatever you were doing before with the results
}
});
I wouldn't change JsonRequestBehavior to AllowGet. Instead I suggest:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class MyAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
OnAuthorizationHelp(filterContext);
}
internal void OnAuthorizationHelp(AuthorizationContext filterContext)
{
if (filterContext.Result is HttpUnauthorizedResult)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.HttpContext.Response.StatusCode = 401;
filterContext.HttpContext.Response.End();
}
}
}
}
and add global js ajax errors handler:
$(document).ajaxError(function (xhr, props) {
if (props.status === 401) {
location.reload();
}
}
Even though this is well past answered, I think this is the shortest and sweetest answer if you are using .NET 4.5. Little property called SuppressFormsAuthenticationRedirect which was added. Set to true and it will not perform the 302 Redirect to login page.
http://msdn.microsoft.com/en-us/library/system.web.httpresponse.suppressformsauthenticationredirect.aspx
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class AjaxAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
// returns a 401 already
base.HandleUnauthorizedRequest(filterContext);
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
// we simply have to tell mvc not to redirect to login page
filterContext.HttpContext.Response.SuppressFormsAuthenticationRedirect = true;
}
}
}
Assuming you plan on handling the ajax requests fail/error callback, in which you will get a 401 Unauthorized.
On Master page add this jquery script ------------
<script type="text/javascript">
$.ajaxSetup({
statusCode: {
403: function () {
window.location.reload();
}
}
});
OR
$.ajaxSetup({
error: function (x, e) {
if (x.status == 403) {
window.location.reload();
}
}
});
</script>
Add a cs file named with TraceFilter in your project and write a seald class TraceFilterAttribute inheriting to ActionFilterAttribute.
Add TraceFilterAttribute class in FilterConfig.cs available in App_Start folder of your project by writing below line.
filters.Add(new TraceFilterAttribute());
Override method OnActionExecuting() in TraceFilterAttribute class. This will automatically check session and if finds session null then calls script available in master page and from their you can go to your choice page.
[AttributeUsage(AttributeTargets.All)]
public sealed class TraceFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext != null)
{
HttpSessionStateBase objHttpSessionStateBase = filterContext.HttpContext.Session;
var userSession = objHttpSessionStateBase["etenetID"];
if (((userSession == null) && (!objHttpSessionStateBase.IsNewSession)) || (objHttpSessionStateBase.IsNewSession))
{
objHttpSessionStateBase.RemoveAll();
objHttpSessionStateBase.Clear();
objHttpSessionStateBase.Abandon();
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.HttpContext.Response.StatusCode = 403;
filterContext.Result = new JsonResult { Data = "LogOut" };
}
else
{
filterContext.Result = new RedirectResult("~/Admin/GoToLogin");
}
}
}
}
}
I was having a similar issue and found this
Instead of returning any JSON, just before the response is sent back, force ASP.NET to return a 401 code. In Global.asax:
protected void Application_EndRequest()
{
var context = new HttpContextWrapper(Context);
if (context.Request.IsAjaxRequest() && context.Response.StatusCode == 302)
{
Context.Response.Clear();
Context.Response.Write("**custom error message**");
Context.Response.StatusCode = 401;
}
}
Then you can let the client deal with it in JavaScript/jQuery or whatever you are using
here is how I handle this in so simple way in my custom authorization , I check if session is out and handle this as un-authorized with a boolean to check if it is really authenticated but not authorized (to redirect to un-authorized page) or it is not authenticated due to session time out ( redirect to Login)
private bool ispha_LoggedIn = false;
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
ispha_LoggedIn = false;
var session = httpContext.Session;
bool authorize = false;
if (httpContext.Session["authenticationInfo"] == null)
{
return authorize;
}
using (OrchtechHR_MVCEntities db = new OrchtechHR_MVCEntities())
{
UserAuthenticationController UM = new UserAuthenticationController();
foreach (var roles in userAssignedRoles)
{
authorize = UM.IsUserInRole(httpContext.User.Identity.Name, roles);
if (authorize)
{
return authorize;
}
}
}
ispha_LoggedIn = true;
return authorize;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (ispha_LoggedIn==false)
{
filterContext.Result = new RedirectResult("~/UserAuthentication/LogIn");
}
else
{
filterContext.Result = new RedirectResult("~/Dashboard/UnAuthorized");
}
}
Hope if this guides someone and please if there're comments its appreciated to know them though.
You might want to try to throw HttpException and catch it in your javascript.
throw new HttpException(401, "Auth Failed")
on ajax call if session expired return something like this
<script>
$(function(){
location.reload();
});
</script>
haha...

Moving between HTTP and HTTPS in ASP.NET MVC

So I've found the [RequiresHttps] attribute but once your in https your kind of stuck there, so to try and be able to have actions on a single url (and scheme) I've found I've ended up having to create my own ExtendedController to revert back to http for actions that don't use [RequireHttps].
Just wondering if what I'm doing is okay or if there is a better way?
public class ExtendedController : Controller
{
protected virtual void HandleHttpRequest(AuthorizationContext filterContext)
{
if (!string.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("Cannot post between https and http.");
}
string url = "http://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl;
filterContext.Result = new RedirectResult(url);
}
protected override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
object[] attributes = filterContext.ActionDescriptor.GetCustomAttributes(true);
if (!attributes.Any(a => a is RequireHttpsAttribute))
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (filterContext.HttpContext.Request.IsSecureConnection)
{
this.HandleHttpRequest(filterContext);
}
}
}
}
What you have is syntatically correct, however a suggestion is to create a new Action filter that inherits from the default RequireHttpsAttribute and takes a parameter to switch between http and https.
public class RequireHttpsAttribute : System.Web.Mvc.RequireHttpsAttribute
{
public bool RequireSecure = false;
public override void OnAuthorization(System.Web.Mvc.AuthorizationContext filterContext)
{
if (RequireSecure)
{
base.OnAuthorization(filterContext);
}
else
{
// non secure requested
if (filterContext.HttpContext.Request.IsSecureConnection)
{
HandleNonHttpRequest(filterContext);
}
}
}
protected virtual void HandleNonHttpRequest(AuthorizationContext filterContext)
{
if (String.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
// redirect to HTTP version of page
string url = "http://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl;
filterContext.Result = new RedirectResult(url);
}
}
}
Then, on your action method or controller you would use:
[RequireHttps (RequireSecure = true)]
...
or
[RequireHttps (RequireSecure = false)]
To make it little more manageable. This solution assumes that majority of your web application use HTTP scheme.
Create new action filter RequiresHttp (use HTTP if NeedSsl attribute is not apply explicitly on action or controller),
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpRequestBase req = filterContext.HttpContext.Request;
HttpResponseBase res = filterContext.HttpContext.Response;
bool needSsl = filterContext.ActionDescriptor.IsDefined(typeof(NeedSslAttribute), true)
|| filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(NeedSslAttribute), true);
if (needSsl && !req.IsSecureConnection) //https: secure
{
var builder = new UriBuilder(req.Url)
{
Scheme = Uri.UriSchemeHttps,
Port = 444
};
res.Redirect(builder.Uri.ToString());
}
else if (!needSsl && req.IsSecureConnection) //http: non secure
{
var builder = new UriBuilder(req.Url)
{
Scheme = Uri.UriSchemeHttp,
Port = 8081
};
res.Redirect(builder.Uri.ToString());
}
base.OnActionExecuting(filterContext);
}
And new blank attribute NeedSSL (for indication purpose)
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class NeedSslAttribute : Attribute { }
Apply RequiresHttp as global action filter in Global.aspx.cs
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new RequiresHttp());
}
Now apply apply NeedSslAttribute on controllers and actions where do you want to use HTTPS scheme
[NeedSsl]
[AllowAnonymous]
public ActionResult LogOn()
This code is not perfect as action filter RequiresHttp does multiple jobs i.e. check NeedSsl attribute and apply HTTP or HTTPS scheme. Would have been better if we could use two action filters RequiresHTTP and RequiresHTTPS.
Now if RequiresHTTP was set as global filter and RequiresHTTPS filter was applied on specific actions and specific RequiresHTTPS filter would have given preference.

Resources