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
Related
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.
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
I am working on a project that is a web application. The application should have a single button on the page that redirects the user back to the same page. In 1 of every 5 (or thereabout) occasions when you press the button the program should throw some exception and log it.
It should catch the exception and do three things: Send an email, write the error in a file and write it to debug window.
My controller so far:
public class HomeController : Controller
{
public ActionResult Index()
{
try
{
RedirectToAction("Index");
}
catch (ArgumentException e)
{
}
Random RandNumber = new Random();
int rand = RandNumber.Next(1000);
if(rand % 5 == 0)
{
throw new System.ArgumentException("This is a random excpetion");
}
return View();
}
}
The idea is the to have the class Logger that declares a collection of the class LogMedia and loops through all the instances.
Class Logger:
public class Logger
{
List<LogMedia> m_loggers = new List<LogMedia>();
void LogException(Exception ex)
{
foreach(var i in m_loggers)
{
//Loop through all Log Media instances
}
}
Class LogMedia
class LogMedia
{
public virtual void LogMessage(string Message); //virtual function that doesen't do anything
}
class OutputWindowLogMedia:LogMedia
{
public override void LogMessage(string Message)
{
System.Diagnostics.Debug.WriteLine(Message);
}
}
class TextFileLogMedia : LogMedia
{
public override void LogMessage(string Message)
{
File.AppendAllText("c:\\Temp\\Log.txt", Message);
}
}
class EmailLogMedia : LogMedia
{
public override void LogMessage(string Message)
{
//send email
}
}
}
My questions to you are the following:
Will my controller work as it stands now with what I am trying to implement? I am especially skeptical of my try catch..
What is the best way to loop though these three instances?
Do I have the right class declerations for Log Media, i.e. should I create new classes(that inherits LogMedia) for each instance and then a function that overrides the virtual function in LogMedia.
It's unclear if you want this behavior on this particular action, the controller, or the entire app. That said, unless there is some specific recovery code you want to build into your logic, I wouldn't pollute my action code with the try-catch.
There are two options the MVC framework provides to handle errors:
First, you can override OnException in a specific controller:
protected override void OnException(ExceptionContext filterContext)
{
// do your logging here
// set this flag if you want to stop the exception from bubbling
filterContext.ExceptionHandled = true;
}
Second, you can create an error handling filter:
public class MyExceptionFilterAttribute :
System.Web.Mvc.FilterAttribute,
System.Web.Mvc.IExceptionFilter
{
public void OnException(System.Web.Mvc.ExceptionContext filterContext)
{
// same error handling logic as the controller override
}
}
A filter can either be added to the global filters list, or applied to an action method like this:
[MyExceptionFilter]
public ActionResult Index()
{
}
Edit: forgot to mention your logger structure. Your approach of having Logger loop over multiple instances of LogMedia is a good, common approach to supporting multiple logging mechanisms with one common interface (log4net appenders for example). That said, have you considered using an existing, proven framework for your logging needs? You get a thoroughly tested framework to run, and a skill that will carry over to future endeavours.
Edit2: after your comment, I took a closer look at your code instead of your exception trapping approach. In your Index action, you're redirecting to Index with no condition checking. This is going to end up as a constant redirect loop (until IIS stops you). Since this is an assignment, I don't want to give too much away, but you will need some logic on the server side to detect a button click and redirect back to your Index. Consider another Index action method that accepts HttpPost (your current action would be the HttpGet handler).
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'm using ravendb as storage backend. Since it uses unit of work pattern I need to open session, perform actions, save results and close session. I want to keep my code clean and don't call session opening and closing explicitly in each action, so I put this code to OnActionExecuting and OnActionExecuted methods, like this:
#region RavenDB's specifics
public IDocumentSession DocumentSession { get; set; }
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.IsChildAction)
{
return;
}
this.DocumentSession = Storage.Instance.OpenSession();
base.OnActionExecuting(filterContext);
}
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (filterContext.IsChildAction)
{
return;
}
if (this.DocumentSession != null && filterContext.Exception == null)
{
this.DocumentSession.SaveChanges();
}
this.DocumentSession.Dispose();
base.OnActionExecuted(filterContext);
}
#endregion
But some actions require connection to ravendb and come don't. So I've decided to create custom attribute and mark methods need to have DocumentSession opened with it. Here is an example:
//
// GET: /Create
[DataAccess]
public ActionResult Create()
{
return View();
}
And I stuck. My plan was to retrieve actions' attributes in the OnActionExecuted method and if [DataAccess] is present, open DocumentSession.
In the OnActionExecuted I can retrieve action name (method's name) via filterContext.ActionDescriptor.ActionName statement. But how I can retrieve method's attributes of the given class using reflection?
I found out that it might be Attribute.GetCustomAttributes call, but closest I got — I need to have MemberInfo object of the method. But how I can get this MemberInfo for method given by name?
If you inherit your custom attribute from FilterAttribute, it will have OnActionExecuted and OnActionExecuting methods. And it will be executed before general OnActionExecuted and OnActionExecuting.
Example:
public class DataAccessAttribute: FilterAttribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.IsChildAction)
{
return;
}
var controller = (YourControllerType)filterContext.Controller;
controller.DocumentSession = Storage.Instance.OpenSession();
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
if (filterContext.IsChildAction)
{
return;
}
var controller = (YourControllerType)filterContext.Controller;
documentSession = controller.DocumentSession;
if (documentSession != null && filterContext.Exception == null)
{
documentSession.SaveChanges();
}
documentSession.Dispose();
}
Why not have your DataAccess attribute inherit from ActionFilterAttribute so you can put the ActionExecuting/Executed methods on the attribute instead of the Controller?
Example of how to do it with NHibernate by using an action filter to set the session on a base controller. It's done using NHibernate, but very similar to what you'd need to do and it's written by Ayende who's one of the RavenDB authors I believe.