I've added a generic handler (KeepSessionAlive.ashx) to the root of my mvc 4 project. The code in the handler is:
public class KeepSessionAlive : IHttpHandler, IRequiresSessionState
{
public void ProcessRequest(HttpContext context)
{
context.Session["KeepSessionAlive"] = DateTime.Now;
}
public bool IsReusable
{
get
{
return false;
}
}
}
Whenever I run my application and check while debugging, I don't see the Session["KeepSessionAlive"] being set. I tried adding a break point in the ProcessRequest method in the handler, but the break point is never hit as I'm surfing the site. Do I need to do anything else to get the application to pick up the handler?
We had done something like in a webforms project and I don't recall having to do anything else in there. Also I've updated my route to include:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("{resource}.ashx/{*pathInfo}");
//other routes skipped
}
I added the .ashx in there in case that was the problem, but either way with or without that line the handler does not seem to get invoked.
You can override the OnActionExecuted method of the Controller.
public class BaseController : Controller
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
filterContext.HttpContext.Session["KeepSessionAlive"] = DateTime.Now;
}
}
After this just derive your controller from this BaseController. This is a more 'MVC-way'.
Another approach is to create a custom ActionFilter and apply it globally, as mentioned by SLaks:
public class KeepSessionAliveAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
filterContext.HttpContext.Session["KeepSessionAlive"] = DateTime.Now;
}
}
You need to remember to register it
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new KeepSessionAliveAttribute());
}
}
PS: You handler might not be working because you haven't registered it on your web.config
Related
I have an WebApi application that uses Simple Injector and I'm trying to configure a particular filter with controller attribute (with parameters). I have this configuration working in another project that uses Ninject, but I don't know how to do this on Simple Injector.
public enum UserType {
Director,
Developer,
Leader
}
My controller:
[RequiresAtLeastOneOfUserTypes(UserType.Developer, UserType.Leader)]
public class MyController : Controller
{
...
}
My Attribute:
public sealed class RequiresAtLeastOneOfUserTypesAttribute : Attribute
{
public UserType[] TypesToBeVerified { get; set; }
public RequiresAtLeastOneOfUserTypesAttribute(params UserType[] typesToBeVerified)
{
TypesToBeVerified = typesToBeVerified;
}
}
My Filter:
public class RequiresAtLeastOneOfUserTypesFilter : IActionFilter
{
private readonly IUser _user;
private readonly UserType[] _typesToBeVerified;
protected RequiresAtLeastOneOfUserTypesFilter(IUser user, params UserType[] typesToBeVerified)
{
_user = user;
_typesToBeVerified = typesToBeVerified;
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
bool authorized = _user.HasAtLeastOneOfTypes(_typesToBeVerified);
if (!authorized)
{
throw new ForbiddenUserException();
}
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
// do nothing
}
}
And finally my Ninject configuration:
this.BindFilter<RequiresAtLeastOneOfUserTypesFilter>(FilterScope.Controller, 0)
.WhenControllerHas<RequiresAtLeastOneOfUserTypesAttribute>()
.WithConstructorArgumentFromControllerAttribute<RequiresAtLeastOneOfUserTypesAttribute>(
"typesToBeVerified",
attribute => attribute.typesToBeVerified);
My question is: How can I do this configuration using Simple Injector?
The Simple Injector Web API integration packages don't contain an integration feature for action filters as Ninject's integration package does. But such integration can be built in a few lines of code.
There are a few options here. The first option is to revert to resolving services directly from inside your action filter, as demonstrated inside the documentation. This approach is fine when you have a single filter class, but isn't the cleanest approach, and would force you to make changes to your already created filter attribute.
As a second option you can, therefore, create a action filter proxy class, that is able to forward the call to your real filter class, which can than be resolved by Simple Injector:
public class ActionFilterProxy<T> : IActionFilter
where T : IActionFilter
{
public ActionFilterProxy(Container container) => _container = container;
public void OnActionExecuting(ActionExecutingContext filterContext) =>
_container.GetInstance<T>().OnActionExecuting(filterContext);
public void OnActionExecuted(ActionExecutedContext filterContext) =>
_container.GetInstance<T>().OnActionExecuted(filterContext);
}
Using this proxy, you can make the following configuration:
GlobalConfiguration.Configuration.Filters.Add(
new ActionFilterProxy<RequiresAtLeastOneOfUserTypesFilter>(container));
container.Register<RequiresAtLeastOneOfUserTypesFilter>();
This still forces you to make a change to RequiresAtLeastOneOfUserTypesFilter, because Simple Injector can't provide the attribute's information (the UserType[]) to RequiresAtLeastOneOfUserTypesFilter's constructor. Instead,you can change RequiresAtLeastOneOfUserTypesFilter to the following:
public class RequiresAtLeastOneOfUserTypesFilter : IActionFilter
{
private readonly IUser _user;
public RequiresAtLeastOneOfUserTypesFilter(IUser user) => _user = user;
public void OnActionExecuting(ActionExecutingContext filterContext)
{
// Get the attribute from the controller here
var attribute = filterContext.ActionDescriptor.ControllerDescriptor
.GetCustomAttribute<RequiresAtLeastOneOfUserTypesAttribute>();
bool authorized = _user.HasAtLeastOneOfTypes(attribute.TypesToBeVerified);
if (!authorized)
{
throw new ForbiddenUserException();
}
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
}
}
A third option to use is the one referred to in the documentation, which is described in this blog post, which discusses a model where you place your filters behind an application-specific abstraction and allow them to be Auto-Registered. It uses the a similar proxy approach. This method is useful when you have multiple/many filters that need to be applied (where their order of execution is irrelevant).
I have an MVC project i which the user can change language from a menu.
The controller code:
[HttpGet]
public ActionResult ChangeLanguage(string Language)
{
Response.Cookies[SessionParams.LANGUAGE].Value = Language;
Response.Cookies[SessionParams.LANGUAGE].Expires = DateTime.Now.AddDays(7);
return Redirect(Request.UrlReferrer.PathAndQuery);
}
and the Global.asax.cs code:
protected void Application_BeginRequest(Object sender, EventArgs e)
{
if (Request.Cookies[SessionParams.LANGUAGE] != null)
{
Thread.CurrentThread.CurrentUICulture = new CultureInfo(Request.Cookies[SessionParams.LANGUAGE].Value);
}
}
This works great. Now I added a class that implements IAuthorizationFilter to make sure that I can check whether the session is still valid before every request (FilterConfig.cs):
public class ConnectedUserValidAuthorizationFilter : IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
UrlHelper urlHelper = new UrlHelper(filterContext.HttpContext.Request.RequestContext);
string loginUrl = urlHelper.Action("Login", "Account");
if (filterContext.HttpContext.Request.Url.AbsolutePath != loginUrl)
{
if (filterContext.HttpContext.Session[SessionParams.CONNECTED_USER] == null)
filterContext.HttpContext.Response.Redirect("~");
}
}
}
For some reason, after I add the filter to the global filters:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new ConnectedUserValidAuthorizationFilter());
}
Debugging shows that Request.Cookies in Global.asax.cs no longer holds value for the language cookie.
Removing the filter brings the value back.
Any idea how to resolve it? I tried moving the filter code to Application_BeginRequest, but the session does not exists yet in that context.
I ended up implementing IActionFilter instead of IAuthorizationFilter interface, with the same logic used in OnAuthorization, inside OnActionExecuting function.
This seems more appropriate for the task, since OnActionExecuting is called before every Action request. It also seems to keep the cookies intact.
If I have a global variable that is false by default but changes later on, is it possible to automatically redirect using this variable? This is basically what I want:
#if (!boolean)
{
<meta http-equiv='refresh' value='0; redirect_to_this_url'>
}
I've tried this, and it doesn't work (the controller loads forst and throws loads of errors based on this boolean) Please help.
This could be tackled using Action Filters
For example, consider what happens when we replace the default FilterConfig.cs file in the App_Start folder of an Asp.Net 4.5.2 MVC project with this:
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new RedirectActionFilter());
}
}
public class RedirectActionFilter : IActionFilter
{
static Random rng = new Random();
static bool GetBool() => rng.Next() % 2 == 0;
public void OnActionExecuted(ActionExecutedContext filterContext)
{
if (GetBool())
{
filterContext.Result = new RedirectResult("http://www.bbc.co.uk");
}
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
}
}
Then the OnActionExecuted() method of our RedirectActionFilter class gets called before the Asp.Net application responds to the browser. As a result, if GetBool() returns true then we will redirect to bbc.co.uk otherwise nothing is changed and we would get the normal action result from whichever controller action is handling our request
i need to hit DB and load the settings file before every page loads. Am currently using MVC and am creating that call in constructor in All controllers.
Am not sure of what is the better way to handle this scenario ?? I read like we can use singleton class in this scenario.
Is it possible to have the data once and reuse across pages ? What is the best way ?
Some sample code snippets will help !
Option one: you can used Application_BeginRequest in Global.asax.cs:
protected void Application_BeginRequest(object sender, System.EventArgs e)
{
//something
}
Option two: create a global filter:
public class ActionLogFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext filterContext)
{
// do your stuff. This is run before control is passed to controller
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
// do stuff here - control here is passed after controller is done with the action execution
}
}
and then add controller to execution stack in Global.asax.cs:
FilterConfig.RegisterGlobalFilters(GlobalFilterCollection filters)
and FilterConfig is usually looks like this:
public static class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new MyFilter());
}
}
Option three: Create your global controller that overrides OnActionExecuting (see the filter example). Make your controllers to inherit from that global base controller.
I prefer option with filters. Favour composition over inheritance
I am doing exceptional handling in MVC in Three models.
public class CustomHandleErrorAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
//logging
}
}
and
public class Base_Application : System.Web.HttpApplication
{
protected void Application_Error(object sender, EventArgs e)
{
//Logging
}
}
and
public class Base_Controller : Controller
{
protected override void OnException(ExceptionContext filterContext)
{
//Logging;
}
}
If i throw a sample exception from Code it is catching by CustomHandleErrorAttribute and Base_Controller .
and
In Base_Application I am logging exceptions same is logging by Elmah.
So I want to know the best way in all cases.
If you have a base controller, I would suggest go with this approach
public class CustomHandleErrorAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
//logging
}
}
And decorate base controller, with custom attribute
[CustomHandleError]
public class Base_Controller : Controller
{
}
So also you can reuse the attribute, if you add a controller which don't need a base controller in the future.
Also go through this wonderful article to decide whether to use a base controller or an ActionFilter.
Application_Error event can be used to catch errors which are not caught by a page-level error handler.