How to access data after redirect - asp.net-mvc

Again there are multiple articles which says how to access data after redirect. but doesn't serves my purpose.
I am having errorcontroller which is having index action method and error index view.
If there is any error in the application it will caught in Application_Error event.
inside Application_Error event I had logged the error and redirected to Error Index page like this -
protected new void Application_Error(object sender, EventArgs e)
{
Exception error = Server.GetLastError();
log.error(error.Message);
HttpContext.Current.Response.Redirect("~/Error/Index");
}
Now in the error index view, I would like to display the error message. What should I do in Application_Error event which can be access by Error Index view?
Updated : I don't want to use Session as session object may not be available in Application_Error event. this is dependent on when the error occurred.

Approach - 1
As per my knowledge you can use TempData to store the posted data. It is like a DataReader Class, once read, Data will be lost. So that stored data in TempData will become null.
var Value = TempData["keyName"] //Once read, data will be lost
So to persist the data even after the data is read you can Alive it like below
var Value = TempData["keyName"];
TempData.Keep(); //Data will not be lost for all Keys
TempData.Keep("keyName"); //Data will not be lost for this Key
TempData works in new Tabs/Windows also, like Session variable does.
You could use Session Variable also, Only major problem is that Session Variable are very heavy comparing with TempData. Finally you are able to keep the data across Controllers/Area also.
Approach - 2
This works for me. This is very easy and no need to consider any change in Web.Config or Register the Action Filter in Global.asax file.
ok. So, First I am creating a simple Action Filter. This will handle Ajax and Non Ajax requests.
public class MyCustomErrorAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
filterContext.ExceptionHandled = true;
var debugModeMsg = filterContext.HttpContext.IsDebuggingEnabled
? filterContext.Exception.Message +
"\n" +
filterContext.Exception.StackTrace
: "Your error message";
//This is the case when you need to handle Ajax requests
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.Result = new JsonResult
{
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
Data = new
{
error = true,
message = debugModeMsg
}
};
}
//This is the case when you handle Non Ajax request
else
{
var routeData = new RouteData();
routeData.Values["controller"] = "Error";
routeData.Values["action"] = "Error";
routeData.DataTokens["area"] = "app";
routeData.Values["exception"] = debugModeMsg;
IController errorsController = new ErrorController();
var exception = HttpContext.Current.Server.GetLastError();
var httpException = exception as HttpException;
if (httpException != null)
{
Response.StatusCode = httpException.GetHttpCode();
switch (System.Web.HttpContext.Current.Response.StatusCode)
{
case 504:
routeData.Values["action"] = "Http404";
break;
}
}
var rc = new RequestContext
(
new HttpContextWrapper(HttpContext.Current),
routeData
);
errorsController.Execute(rc);
}
base.OnException(filterContext);
}
}
Now you can implement this Action Filter on Controller as well as on the Action only.Example:
I am going little off topic. I thought this is bit important to explain.
If you pay attention to the above highlighted part. I have specified the order of the Action Filter. This basically describes the order of execution of Action Filter. This is a situation when you have multiple Action Filters implemented over Controller/Action Method
This picture just indicates that let's say you have two Action Filters. OnActionExecution will start to execute on Priority and OnActionExecuted will start from bottom to Top. That means in case of OnActionExecuted Action Filter having highest order will execute first and in case of OnActionExecuting Action Filter having lowest order will execute first. Example below.
public class Filter1 : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//Execution will start here - 1
base.OnActionExecuting(filterContext);
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
//Execution will move here - 5
base.OnActionExecuted(filterContext);
}
}
public class Filter2 : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//Execution will move here - 2
base.OnActionExecuting(filterContext);
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
//Execution will move here - 4
base.OnActionExecuted(filterContext);
}
}
[HandleError]
public class HomeController : Controller
{
[Filter1(Order = 1)]
[Filter2(Order = 2)]
public ActionResult Index()
{
//Execution will move here - 3
ViewData["Message"] = "Welcome to ASP.NET MVC!";
return View();
}
}
You may already aware that there are different types of filters within MVC framework. They are listed below.
Authorization filters
Action filters
Response/Result filters
Exception filters
Within each filter, you can specify the Order property. This basically describes the order of execution of the Action Filters.

Use TempData for getting value.
Some feature about TempData
TempData is a dictionary object that is derived from TempDataDictionary class and stored in short lives session.
TempData is used to pass data from current request to subsequent request means incase of redirection.
It’s life is very short and lies only till the target view is fully loaded.
It’s required typecasting for complex data type and check for null values to avoid error.
It is used to store only one time messages like error messages, validation messages.

Related

MVC 4 AuthorizeAttribute.HandleUnauthorizedRequest ViewResult - infinite loop

i've stepped through my code a million times and can't find a problem with my implementation..
in custom AuthorizeAttribute i overwrote 2 methods
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (!httpContext.Request.IsAuthenticated)
return false;
var routeData = httpContext.Request.RequestContext.RouteData;
var ctrl = routeData.Values["controller"].ToString();
var action = routeData.Values["action"].ToString();
var user = httpContext.User.Identity.Name;
_logger.Info("[logging all the details]");
return ctrl == "SomeController";
}
protected override void HandleUnauthorizedRequest(AuthorizationContext ctx)
{
ctx.Result = new ViewResult { ViewName = "Unauthorized" };
// base.HandleUnauthorizedRequest(ctx);
}
the authorization logic is mocked to return false only on specific controller, and i've stepped through this to verify it's working correctly.
above code will cause infinite loop. in my log i can see that line hit 666 times (coincidence?) ..
if i do call base.HandleUnauthorizedRequest(ctx), all i get is a blank page. so i reflected what the base does, and it's this
filterContext.Result = new HttpUnauthorizedResult();
so this explains why it renders a blank page instead of redirecting to Unauthorized.cshtml. what i'm not sure about, is why does it go into an infinite loop if i don't call the base.
p.s.
i've verified that if i put the wrong Unauthorized view it will error out (but still hangs indefinitely)
System.InvalidOperationException: The view 'Unauthorized11' or its master was not found or no view engine supports the searched locations
Here is the implementation that i ended up going with and it's working very well.
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
// this is overriden for kendo menus to hide
var ctrl = filterContext.RequestContext.RouteData.GetRequiredString("controller");
var action = filterContext.ActionDescriptor.ActionName;
[custom authorization logic on action/ctrl]
// useful to determine if it's authorizing current controller path or menu links
var path = filterContext.HttpContext.Request.PhysicalPath;
_authorizingCurrentPath = path.Contains(ctrl) || path.EndsWith("WebUI") ;
if (userAuth < requiredAuth)
HandleUnauthorizedRequest(filterContext);
}
protected override void HandleUnauthorizedRequest(AuthorizationContext ctx)
{
if (!ctx.HttpContext.User.Identity.IsAuthenticated)
base.HandleUnauthorizedRequest(ctx);
else {
if (_authorizingCurrentPath) {
// handle controller access
ctx.Result = new ViewResult { ViewName = "Unauthorized" };
ctx.HttpContext.Response.StatusCode = 403;
}
else {
// handle menu links
ctx.Result = new HttpUnauthorizedResult();
ctx.HttpContext.Response.StatusCode = 403;
}
}
}
The default implementation of AuthorizeAttribute sets the response on the action context, by not calling into the base the response is never set which causes the filter to repeat the authorization process until a response is set (hence the infinite loop).
You can see this logic in the AuthorizationFilterAttribute class which AuthorizeAttribute derives from.

How to handle business logic errors and application exceptions from business logic layer in MVC ASP.NET

I'm working on an ASP.NET MVC application in which I need to trap errors in business logic classes and redirect to error controller. I need to trap both errors, ones which are related to my business logic and others which are application exceptions. If I throw an exception from my business logic, how do I catch that in the current controller and how do I redirect to ErrorController?
You can decorate your Controller/Action with the [HandleErrorAttribute] to do just that.
For example:
[HandleError]
public ActionResult PlaceOrder(OrderDetails orderDetails)
{
orderService.PlaceOrder(orderDetails);
return View("Success");
}
You can set the appropriate View to load to depend on the Exception Type:
[HandleError(ExceptionType=typeof(PlaceOrderException),View="OrdersError"]
[HandleError(ExceptionType=typeof(Exception),View="GeneralError"]
public ActionResult PlaceOrder(OrderDetails orderDetails)
{
orderService.PlaceOrder(orderDetails);
return View("Success");
}
Alternatively, you can register it globally on your global.asax:
GlobalFilters.Filters.Add(new HandleErrorAttribute
{
View = "Error"
});
P.S: The above example assumes your 'Error/GeneralError/OrdersError' Views are in the Shared folder. if they're not, you're gonna need to specify the full path.
Edit (as per your comment):
If you want to return Json instead of View, create the following ActionFilter:
public class HandleErrorJsonAttribute : FilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.StatusCode = 500;
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
filterContext.Result = new JsonResult
{
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
ContentType = "application/json",
Data = new
{
Msg = "An Error Occured",
ExceptionMsg = filterContext.Exception.ToString()
}
};
}
}
Then use the new [HandleErrorJson] attribute (as outlined above), or register it as a Global Filter in your global.asax.
I think it is a good idea to look at this sample. When ever an error occurs in Business logic layer you can return false or say -1 for a method of Business class, then show proper error message to user, and in catch part of the business method you can use one of popular error logging libs like Log4NET or elmah.
Edited:
To redirect user when error occurs you can specify the error controller in custom error section of web config

Best approach to don't request same info over and over

On my controller I have it inherit a MainController and there I override the Initialize and the OnActionExecuting.
Here I see what is the URL and by that I can check what Client is it, but I learned that for every Method called, this is fired up again and again, even a simple redirectToAction will fire the Initialization of the same controller.
Is there a better technique to avoid this repetition of database call? I'm using Entity Framework, so it will take no time to call the DB as it has the result in cache already, but ... just to know if there is a better technique now in MVC3 rather that host the variables in a Session Variable
sample code
public class MyController : MainController
{
public ActionResult Index()
{
return View();
}
}
public class MainController : Controller
{
public OS_Clients currentClient { get; set; }
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
// get URL Info
string url = requestContext.HttpContext.Request.Url.AbsoluteUri;
string action = requestContext.RouteData.GetRequiredString("action");
string controller = requestContext.RouteData.GetRequiredString("controller");
object _clientUrl = requestContext.RouteData.Values["cliurl"];
if (_clientUrl != null && _clientUrl.ToString() != "none")
{
// Fill up variables
this.currrentClient = db.FindClientById(_clientUrl.ToString());
}
base.Initialize(requestContext);
}
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
// based on client and other variables, redirect to Disable or Login Actions
// ... more code here like:
// filterContext.Result = RedirectToAction("Login", "My");
base.OnActionExecuting(filterContext);
}
}
is it still best to do as:
public OS_Clients currentClient {
get {
OS_Clients _currentClient = null;
if (Session["CurrentClient"] != null)
_currentClient = (OS_Clients)Session["CurrentClient"];
return _currentClient;
}
set {
Session["CurrentClient"] = value;
}
}
It seems that you dealing with application security in that case I would suggest to create Authorization filter, which comes much early into the action. You can put your permission checking code over there and the framework will automatically redirect the user to login page if the permission does not meet AuthorizeCore.
Next, if the user has permission you can use the HttpContext.Items as a request level cache. And then you can create another ActionFilter and in action executing or you can use the base controller to get the user from the Httpcontext.items and assign it to controller property.
If you are using asp.net mvc 3 then you can use the GlobalFilters to register the above mentioned filters instead of decorating each controller.
Hope that helps.
In your base controller, you need to cache the result of the first call in a Session variable.
This makes sure the back-end (DB) is not called unnecessarily, and that the data is bound to the user's Session instead of shared across users, as would be the case with the Application Cache.

OutputCache behavior in ASP.NET MVC 3

I was just testing Output Caching in the RC build of ASP.NET MVC 3.
Somehow, it is not honoring the VaryByParam property (or rather, I am not sure I understand what is going on):
public ActionResult View(UserViewCommand command) {
Here, UserViewCommand has a property called slug which is used to look up a User from the database.
This is my OutputCache declaration:
[HttpGet, OutputCache(Duration = 2000, VaryByParam = "None")]
However, when I try and hit the Action method using different 'slug' values (by manupulating the URL), instead of serving wrong data (which I am trying to force by design), it is instead invoking the action method.
So for example (in order of invocation)
/user/view/abc -> Invokes action method with slug = abc
/user/view/abc -> Action method not invoked
/user/view/xyz -> Invokes action method again with slug = xyz! Was it not supposed to come out of the cache because VaryByParam = none?
Also, what is the recommended way of OutputCaching in such a situation? (example above)
Just wanted to add this information so that people searching are helped:
The OutputCache behavior has been changed to be 'as expected' in the latest release (ASP.NET MVC 3 RC 2):
http://weblogs.asp.net/scottgu/archive/2010/12/10/announcing-asp-net-mvc-3-release-candidate-2.aspx
Way to go ASP.NET MVC team (and Master Gu)! You all are awesome!
VaryByParam only works when the values of the url look like /user/view?slug=abc. The params must be a QueryString parameter and not part of the url like your above examples. The reason for this is most likely because Caching happens before any url mapping and that mapping isn't included in the cache.
Update
The following code will get you where you want to go. It doesn't take into account stuff like Authorized filters or anything but it will cache based on controller/action/ids but if you set ignore="slug" it will ignore that particular attribute
public class ActionOutputCacheAttribute : ActionFilterAttribute {
public ActionOutputCacheAttribute(int cacheDuration, string ignore) {
this.cacheDuration = cacheDuration;
this.ignore = ignore;
}
private int cacheDuration;
private string cacheKey;
private string ignore;
public override void OnActionExecuting(ActionExecutingContext filterContext) {
string url = filterContext.HttpContext.Request.Url.PathAndQuery;
this.cacheKey = ComputeCacheKey(filterContext);
if (filterContext.HttpContext.Cache[this.cacheKey] != null) {
//Setting the result prevents the action itself to be executed
filterContext.Result =
(ActionResult)filterContext.HttpContext.Cache[this.cacheKey];
}
base.OnActionExecuting(filterContext);
}
public override void OnActionExecuted(ActionExecutedContext filterContext) {
//Add the ActionResult to cache
filterContext.HttpContext.Cache.Add(this.cacheKey, filterContext.Result,null, DateTime.Now.AddSeconds(cacheDuration),
System.Web.Caching.Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
//Add a value in order to know the last time it was cached.
filterContext.Controller.ViewData["CachedStamp"] = DateTime.Now;
base.OnActionExecuted(filterContext);
}
private string ComputeCacheKey(ActionExecutingContext filterContext) {
var keyBuilder = new StringBuilder();
keyBuilder.Append(filterContext.ActionDescriptor.ControllerDescriptor.ControllerName);
keyBuilder.Append(filterContext.ActionDescriptor.ActionName);
foreach (var pair in filterContext.RouteData.Values) {
if (pair.Key != ignore)
keyBuilder.AppendFormat("rd{0}_{1}_", pair.Key.GetHashCode(), pair.Value.GetHashCode());
}
return keyBuilder.ToString();
}
}

repeat common error info logic in ActionFilterAttribute

I'm implementing a web API using the REST for ASP.NET MVC framework (MVC 2). I want to encapsulate this code, ideally in an ActionFilterAttribute (?), so that I can decorate specific actions that always perform this same logic:
if (!ModelState.IsValid) {
return View(
new GenericResultModel(){ HasError=True, ErrorMessage="Model is invalid."});
}
I really don't want to have to copy and paste this boilerplate code into every controller action where I need to do this.
In this web API scenario, I need to be able to do something like this so that the caller can get a result back in JSON or POX form and see that there is an error. In an ASPX view, obviously I wouldn't need something like this as the validation controls would take care of notifying the user of a problem. But I do not have an ASPX view - I am only returning JSON or POX data serialized from my model.
I've started with this code in an ActionFilter but am not sure what to do next (or if it is even the right starting point):
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
bool result = filterContext.Controller.ViewData.ModelState.IsValid;
if (!result)
{
GenericResultModel m = new GenericResultModel() { HasError = true };
// return View(m)
// ?????
}
base.OnActionExecuting(filterContext);
}
How do I accomplish this?
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Notice that the controller action hasn't been called yet, so
// don't expect ModelState.IsValid=false here if you have
// ModelState.AddModelError inside your controller action
// (you shouldn't be doing validation in your controller action anyway)
bool result = filterContext.Controller.ViewData.ModelState.IsValid;
if (!result)
{
// the model that resulted from model binding is not valid
// => prepare a ViewResult using the model to return
var result = new ViewResult();
result.ViewData.Model = new GenericResultModel() { HasError = true };
filterContext.Result = result;
}
else
{
// call the action method only if the model is valid after binding
base.OnActionExecuting(filterContext);
}
}

Resources