Consider an ASP.NET MVC application that requires a session variable be set. It's used throughout the app. It'll be set by either reading a hashed value on the browser cookie, or after having the user login.
In the WebForms + Master Page model, I'd check the Page_Load() of the master page. Perhaps not the ultimate event, but it was an easy one to find.
How would you check and enforce the existence of a session variable in ASP.NET MVC? Consider that this question might not involve user login details, but some other piece of data (first visit time, perhaps).
Solution Attempts
public void Application_BeginRequest(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Session["SomeDateTime"] = DateTime.Now.ToString();
// results in Object reference not set to an instance of an object.
// context.Session is null
}
You have two options.
1.Place logic in base controller's Initialize function
Assuming that all your controllers inherit from a base controller, you can place the logic needed in the override of the Execute() function of the base controller.
public class BaseController : Controller
{
public BaseController()
{
}
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
// check if the user has the value here using the requestContext.HttpContext object
}
{
2. Use the Global.asax void Application_PreRequestHandlerExecute(Object source, EventArgs e) function
public void Application_PreRequestHandlerExecute(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
// use an if statement to make sure the request is not for a static file (js/css/html etc.)
if(context != null && context.Session != null)
{
// use context to work the session
}
}
Note: The second part works with any ASP.NET application, WebForms or MVC.
As for enforcing that they have a certain session variable, its very open really. You can redirect to a certain page for them to fill out a form or select an option or something. Or maybe just have a default value that is set to a certain session key if it is not found.
EDIT
While playing with this, I noticed a big issue with Application_PreRequestHandlerExecute approach. The event handler is being called for any request done to the server, be it .css/.js/.html files. I'm not sure if this is an issue with the way my workstation is setup, or just how ASP.NET/IIS works, so I would make sure that this isn't being called on all requests when implementing the approach above.
It is for the previous reasons I wrapped the work to be done in the session with an if statement.
Not sure I fully understand the question, but I do this by override the OnActionExecuting method of the controller.
In there you do the code to see if the Session Variable exists. If not, create it, if so then use it.
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.HttpContext.Session != null)
{
//TODO: Get value from session etc.
}
base.OnActionExecuting(filterContext);
}
As another alternative, the ControllerActionInvoker class invokes every action method; it gets assigned to the controller via the controller factory. So you could subclass this action invoker, everytime an action is invoked (by overridding the InvokeAction method) check here for this existence...
Related
I want to keep a log of all the requests to my MVC 3 app, including requested URL, user's ip adress, user agent, etc. Where is the best place to do this,
1) use a base controller?
2) use an action filter?
3) others?
I do this inside my BaseController. Something like this:
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
// If in Debug mode...
if (filterContext.HttpContext.IsDebuggingEnabled)
{
var message = string.Format(CultureInfo.InvariantCulture,
"Leaving {0}.{1} => {2}",
filterContext.Controller.GetType().Name,
filterContext.ActionDescriptor.ActionName.Trim(),
filterContext.Result);
Logger.Debug(message);
}
// Logs error no matter what
if (filterContext.Exception != null)
{
var message = string.Format(CultureInfo.InvariantCulture,
"Exception occured {0}.{1} => {2}",
filterContext.Controller.GetType().Name,
filterContext.ActionDescriptor.ActionName.Trim(),
filterContext.Exception.Message);
Logger.Error(message);
}
base.OnActionExecuted(filterContext);
}
Hope you get the idea.
You can also log before the action is executed using:
protected override void OnActionExecuting(ActionExecutingContext filterContext)
An HttpModule seems like the best fit, if you ask me.
All of the data you're talking about logging is available well before any particular Controller gets invoked. So if you do logging outside of the controller, then you get to capture even those requests which are not to valid controller actions. And there's no need to clutter your controller code with something that's really a cross-cutting concern.
You can have multiple action methods triggered when rendering a single request. Consider using RenderAction on your layout page as follows:
Html.RenderAction("Navigation", "Menu")
It's worth noting that you'd then have two log entries with the same information if you choose to use action filter for logging.
some of my controller actions require a user to be authenticated. Those actions are flagged with a custom [Authorize] attribute. Behind the scene, a custom membership provider does some magic, among which setting some temporary data into the common-thread.
At the end of each action that required an authentication, a call to the OnActionExecuted() filter is required to cleanup the thread. This is done via another custom attribute called [CleanupContext].
So my actions look like this:
[Authorize]
[CleanupContext]
public ViewResult Action()
{
...
}
Since those two are always used together, since I am lazy and since I fear that someday one dev might forget to put one or the other and we end up with some weird behavior: is there a way to combine them into one attribute?
[AuthorizeAndCleanup]
public ViewResult Action()
{
// Aaah, if only it could look like this :D
}
Thanks a lot!
You could derive from AuthorizeAttribute in order to do your custom authorization stuff and implement IActionFilter in order to have access to the OnActionExecuting and OnActionExecuted events (to do your custom cleanup code):
public class AuthorizeAndCleanupAttribute : AuthorizeAttribute, IActionFilter
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
// TODO: your custom authorization logic
return base.AuthorizeCore(httpContext);
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
// TODO: your custom cleanup code
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
}
}
Obviously you should be aware that neither the OnActionExecuting or the OnActionExecuted events will ever be executed if the authorization fails (a.k.a. the AuthorizeCore method returns false) so make sure you do your cleanup in this method if you are about to return false.
A quick. dirty and (possibly) slow solution that I can think of is to skip the cleanup attribute and check for the presence of the custom Authorize attribute in the OnActionExecuted() and execute any cleanup code if you find it (since you stated that they are always present together).
You should implement your own filter provider (http://bradwilson.typepad.com/blog/2010/07/service-location-pt4-filters.html) which will automatically add Cleanup attribute to any action marked by Authorize.
Background:
MVC3 intranet application using windows authentication. After windows authentication completes, a HttpModule looks up the user's network id from an HR database and returns the user's employee information and sets it in HttpContext.Items. I have a base controller that looks for this information and sets a ViewBag property by overriding OnActionExecuting.
My question is that this HttpContext.Items["UserInfo"] information only seems to be available on Home/Index only and not available when I click to Home/About or Home/Help although HomeController inherits BaseController. Can anyone shed light on why this is happening?
protected override void OnActionExecuting(ActionExecutingContext ctx)
{
if (this.HttpContext.Items["UserInfo"] != null)
{
UserInfo User = (UserInfo)this.HttpContext.Items["UserInfo"];
ViewBag.CurrentUser = User;
}
base.OnActionExecuting(ctx);
}
HttpContext.Items is per request only; it is not retained when you redirect to another view or even post back within the current view. So you need to use Session or something else to persist it.
Let me give you some background on my scenario. I got a multi language site and my cultures are stored in database and got a property that is public bool Active { get; set; }
If you come to my site with lets say a Russian culture on your browser, that is not supported on my site so i need to set the culture to "se" (or whatever).
If you come to my site with a supported culture but its not Active i need to set it to a default one "se" (or whatever).
Now I can do this check easy in my override IHttpHandler GetHttpHandler method. The thing is I dont want to make the call to the database everytime the site makes any call on the site. My thought is that I do a session["firstVisit"] to reduce that check but I kinda don't know how i should go about to that, because my scenario it say that
not set to an instance of an object on the session["firstVisit"] line, so my question is how do I handle this? And what other options I got to go about this?
my thought is something like this
protected override IHttpHandler GetHttpHandler(System.Web.Routing.RequestContext requestContext)
{
String culture = requestContext.RouteData.Values["culture"].ToString();
if (HttpContext.Current.Session["firstVisit"].ToString() == string.Empty)
{
//do the check
}
var ci = new CultureInfo(culture);
Thread.CurrentThread.CurrentUICulture = ci;
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(ci.Name);
return base.GetHttpHandler(requestContext);
}
EDIT Doh why did I not think of that, I ended up just doing a check in the protected void Application_Start() to avoid sessions
Use global.asax.cs:
protected void Session_Start( object sender, EventArgs e )
This method is called when the session is first created. You can do the check then, stuff your language info in the session and have it for as long as the session is live through the user's interaction with your site.
I have multi-tenant ASP.NET MVC application which utilizes subdomains to determine the current tenant. Whether or not the domain is valid is determined via database table lookup.
Where would be the best place to have a function that checks if the domain is in the database?If the subdomain is not in the database, it should redirect to the Index action in the Error controller.
Placing the check in the Application_BeginRequest method in the Global.asax file doesn't work because a never ending redirect results.
Where would be the best place to have a function that checks if the domain is in the database?If the subdomain is not in the database, it should redirect to the Index action in the Error controller.
Placing the check in the Application_BeginRequest method in the Global.asax file doesn't work because a never ending redirect results.
That's the right place, you just need to check the request Url is not already /Error.
You might already be doing so, but I'd like to add that it seems pretty static information that you should cache instead of hitting the database for each request.
u can subclass actionFilter attribute and override onactionExecuting method. in this method u can make any database checks and redirect the user appropriately
public class CustomActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if(DatabaseLookup)
{
return;
}
filterContext.Result = new RedirectResult("http://servername/Error");
}
}
now u can decorate ur action methods with this custom actionfilter attribute
[CustomActionFilter]
public ActionResult mymethod()
{
//action method goes here
}