I have a multi-page form in my application, and as such, each method posts to the next. This works fine unless you try to visit the URL of one the methods decorated with [HttpPost].
Is there any way I can route all 404 requests within this specific controller to the Index method?
I will post this as an answer because I am not able to add it as comment
have a look to this link, it might help you
The idea you can catch the error in the OnActionExecuting and there you can make redirect
also as mentioned in this page in the answer, you can handle the Controller.OnException
public class BaseController: Controller
{
protected override void OnException(ExceptionContext filterContext)
{
// Bail if we can't do anything; app will crash.
if (filterContext == null)
return;
// since we're handling this, log to elmah
var ex = filterContext.Exception ?? new Exception("No further information exists.");
LogException(ex);
filterContext.ExceptionHandled = true;
var data = new ErrorPresentation
{
ErrorMessage = HttpUtility.HtmlEncode(ex.Message),
TheException = ex,
ShowMessage = !(filterContext.Exception == null),
ShowLink = false
};
filterContext.Result = View("Index", data); // to redirect to the index page
}
}
after this you can let all your controller to inhert from BaseController
Related
So I have two custom authorize attributes: 1) is to redirect the user to login whenever a session has expired or not authenticated; 2) is currently in progress.
The idea for the second custom authorize attribute is to redirect the user to the same page before he/she navigated to the next page or prevent from redirecting to the next page request. Let say the code is
public class CustomAuth2Attribute : AuthorizeAttribute
{
private const string _errorController = "Error";
public override void OnAuthorization(AuthorizationContext filterContext)
{
var controller = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
var action = filterContext.ActionDescriptor.ActionName;
var area = "";
if (filterContext.RouteData.DataTokens.ContainsKey("area"))
area = filterContext.RouteData.DataTokens["area"].ToString();
if (controller == _errorController)
{
return;
}
// checking the user identity whether the user is allowed to access this page
// then redirect to the previous page before this request and add flash note: "not allowed to access the content"
}
}
The idea is if the user do not have access to a certain page I do not flag this as not authorize instead I should be returning them to the page they were before with the note message.
Also tried the below code:
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new
{
controller,
action,
area
}));
I'm getting too many redirects which is because I'm referencing the current controller, action, and area instead of the previous one. I also tried getting the UrlReferrer value but this is always null.
Any way I can achieve this? Any help is appreciated. Thank you in advance.
You can override HandleUnauthorizedResult for that:
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
base.HandleUnauthorizedRequest(filterContext);
filterContext.Result = new RedirectResult(filterContext.HttpContext.Request.UrlReferrer.ToString());
}
I have overridden the HandleUnauthorizedRequest method in my asp.net mvc application to ensure it sends a 401 response to unauthorized ajax calls instead of redirecting to login page. This works perfectly fine when I run it locally, but my overridden method doesn't get called once I deploy to IIS. The debug point doesn't hit my method at all and straight away gets redirected to the login page.
This is my code:
public class AjaxAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
filterContext.Result = new JsonResult
{
Data = new
{
success = false,
resultMessage = "Errors"
},
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
filterContext.HttpContext.Response.End();
base.HandleUnauthorizedRequest(filterContext);
}
else
{
var url = HttpContext.Current.Request.Url.AbsoluteUri;
url = HttpUtility.UrlEncode(url);
filterContext.Result = new RedirectResult(ConfigurationManager.AppSettings["LoginUrl"] + "?ReturnUrl=" + url);
}
}
}
and I have the attribute [AjaxAuthorize] declared on top of my controller. What could be different once it's deployed to IIS?
Update:
Here's how I'm testing, it's very simple, doesn't even matter whether it's an ajax request or a simple page refresh after the login session has expired -
I deploy the site onto my local IIS
Login to the website, go to the home page - "/Home"
Right click on the "Logout" link, "Open in a new tab" - This ensures that the home page is still open on the current tab while
the session is logged out.
Refresh Home page. Now here, the debug point should hit my overridden HandleUnauthorizedRequest method and go through the
if/else condition and then redirect me to login page. But it
doesn't! it just simply redirects to login page straight away. I'm
thinking it's not even considering my custom authorize attribute.
When I run the site from visual studio however, everything works fine, the control enters the debug point in my overridden method and goes through the if/else condition.
When you deploy your web site to IIS, it will run under IIS integrated mode by default. This is usually the best option. But it also means that the HTTP request/response model isn't completely initialized during the authorization check. I suspect this is causing IsAjaxRequest() to always return false when your application is hosted on IIS.
Also, the default HandleUnauthorizedRequest implementation looks like this:
protected virtual void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
// Returns HTTP 401 - see comment in HttpUnauthorizedResult.cs.
filterContext.Result = new HttpUnauthorizedResult();
}
Effectively, by calling base.HandleUnauthorizedRequest(context) you are overwriting the JsonResult instance that you are setting with the default HttpUnauthorizedResult instance.
There is a reason why these are called filters. They are meant for filtering requests that go into a piece of logic, not for actually executing that piece of logic. The handler (ActionResult derived class) is supposed to do the work.
To accomplish this, you need to build a separate handler so the logic that the filter executes waits until after HttpContext is fully initialized.
public class AjaxAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new AjaxHandler();
}
}
public class AjaxHandler : JsonResult
{
public override void ExecuteResult(ControllerContext context)
{
var httpContext = context.HttpContext;
var request = httpContext.Request;
var response = httpContext.Response;
if (request.IsAjaxRequest())
{
response.StatusCode = (int)HttpStatusCode.Unauthorized;
this.Data = new
{
success = false,
resultMessage = "Errors"
};
this.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
base.ExecuteResult(context);
}
else
{
var url = request.Url.AbsoluteUri;
url = HttpUtility.UrlEncode(url);
url = ConfigurationManager.AppSettings["LoginUrl"] + "?ReturnUrl=" + url;
var redirectResult = new RedirectResult(url);
redirectResult.ExecuteResult(context);
}
}
}
NOTE: The above code is untested. But this should get you moving in the right direction.
i'm trying to handle the following issue
'A potentially dangerous Request.Form value was detected from the
client'
However, I can't find a good tutorial which explains how to catch this error and handle it. Just to be clear I want to stop users from entering any sort of markup when filling out my form. Most of the articles dealing with this issue suggest turning off request validation. This is not something I want to do. I want to catch the error and playback the error to the user. Any help would be greatly appreciated.
You can catch the error with an exception filter. Something like:
public class RequestValidationExceptionFilter : IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
if(filterContext.Exception is HttpRequestValidationException)
{
filterContext.Result = new RedirectResult("/Error");
filterContext.ExceptionHandled = true;
}
}
}
You can get some more info in the Message of the exception, e.g.:
A potentially dangerous Request.QueryString value was detected from the client (FilterName="<script>alert("!!")<...").
But that is not really something to be shown to the user. So best thing to do would be a redirect to some generic error page. Or you can send them back to the current page.
I was doing some quick testing just now, and for some reason action filters and exception filters were not getting executed for me when the validation exception was happening - so I whipped up something quick using Application_Error(). (you can create this method in Global.asax.cs if it does not already exist)
protected void Application_Error()
{
var lastError = Server.GetLastError() as HttpRequestValidationException;
if (lastError == null)
return;
MvcHandler mvcHandler = Context.CurrentHandler as MvcHandler;
if (mvcHandler == null)
return;
RequestContext requestContext = mvcHandler.RequestContext;
if (requestContext == null)
return;
Server.ClearError();
Response.Clear();
Response.TrySkipIisCustomErrors = true;
// pick one of the following two options, or maybe more?
RedirectToUrl(requestContext);
ExecuteActionResult(requestContext, ...);
}
void ExecuteActionResult(RequestContext requestContext, ActionResult result)
{
string controllerName = requestContext.RouteData.GetRequiredString("controller");
IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory();
IController controller = factory.CreateController(requestContext, controllerName);
ControllerContext controllerContext = new ControllerContext(requestContext, (ControllerBase)controller);
result.ExecuteResult(controllerContext);
}
void RedirectToUrl(RequestContext requestContext)
{
requestContext.HttpContext.Server.TransferRequest($"~/Error/Something", false);
}
I included an example of how to redirect to an arbitrary url, and also an example of how to execute a new ActionResult against the same controller that the original request was executed on.
I created a custom exception handling method as shown below and I can catch the database constraint exception with it and return the error to error method of AJAX call. However, when trying to create an exception using throw new Exception()" or throw new ArgumentNullException("instance") I encounter an error as displayed on the image below. Is there any mistake in the custom method? Or how can I test properly test it if it works for AJAX request and Normal request? Any help would be appreciated...
public class CustomErrorHandler : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
//If the request is AJAX return JSON, else return View
if (filterContext.HttpContext.Request.IsAjaxRequest() && filterContext.Exception != null)
{
// Log exception first
filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
filterContext.Result = new JsonResult
{
Data = new
{
success = false,
message = "Error occured",
type = filterContext.Exception.GetType().Name,
exception = filterContext.Exception.ToString(),
number = ((System.Data.SqlClient.SqlException)filterContext.Exception.InnerException.InnerException).Number
},
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
// Let the system know that the exception has been handled
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
}
else
{
// Normal Exception. So, let it handle by its default ways
base.OnException(filterContext);
}
}
}
I have a subscription based MVC 2 application with the basic .NET Membership service in place (underneath some custom components to manage the account/subscription, etc). Users whose accounts have lapsed, or who have manually suspended their accounts, need to be able to get to a single view in the system that manages the status of their account. The controller driving that view is protected using the [Authorize] attribute.
I want to ensure that no other views in the system can be accessed until the user has re-activated their account. In my base controller (from which all my protected controllers derive) I tried modifying the OnActionExecuting method to intercept the action, check for a suspended account, and if it's suspended, redirect to the single view that manages the account status. But this puts me in an infinite loop. When the new action is hit, OnActionExecuting gets called again, and the cycle keeps going.
I don't really want to extend the [Authorize] attribute, but can if need be.
Any other thoughts on how to do this at the controller level?
EDIT: in the base controller, I was managing the redirect (that subsequently created the redirect loop) by modifying the filterContext.Result property, setting it to the RedirectToAction result of my view in question. I noticed everytime the loop occurs, filterContext.Result == null. Perhaps I should be checking against a different part of filterContext?
Ok, so here's my solution in case it helps anyone else. There's got to be a more elegant way to do this, and I'm all ears if anyone has a better idea.
In my BaseController.cs:
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
ViewData["CurrentUser"] = CurrentUser; // this is a public property in the BaseController
if (CurrentUser != null && CurrentUser.Account.Status != AccountStatus.Active)
{
// if the account is disabled and they are authenticated, we need to allow them
// to get to the account settings screen where they can re-activate, as well as the logoff
// action. Everything else should be disabled.
string[] actionWhiteList = new string[] {
Url.Action("Edit", "AccountSettings", new { id = CurrentUser.Account.Id, section = "billing" }),
Url.Action("Logoff", "Account")
};
var allowAccess = false;
foreach (string url in actionWhiteList)
{
// compare each of the whitelisted paths to the raw url from the Request context.
if (url == filterContext.HttpContext.Request.RawUrl)
{
allowAccess = true;
break;
}
}
if (!allowAccess)
{
filterContext.Result = RedirectToAction("Edit", "AccountSettings", new { id = CurrentUser.Account.Id, section = "billing" });
}
}
base.OnActionExecuting(filterContext);
}