When moving to MVC, and now IIS7, we started having issues in that our HTTP Module that opens and closes the ISession was called on every request (static files, etc.). I wanted to avoid doing a full rewrite of NH session management, so I implemented this code in my module, to filter out everything but requests going to the mvchandler:
void context_PreRequestHandlerExecute(object sender, System.EventArgs e)
{
HttpContext context = ((HttpApplication)sender).Context;
Type mvcht = typeof(System.Web.Mvc.MvcHandler);
if (context.Handler != null && context.Handler.GetType().IsAssignableFrom(mvcht))
{
// Code Here
}
}
My question is, I have never used this event in the request pipline. So, are there any hidden pitfalls in doing this? Also, am I looking at a performance issue in running this check for every request? I haven't noticed anything yet, but this is a new and still small app.
Although this doesn't specifically address your question, it should be noted that the cost of opening a session is very minimal. So you may consider not even performing this check in the first place.
Related
I am using ASP.NET MVC 3 to build a web application.
What I am trying to do is pass values between two controllers, though there are many ways to do this I am particular interested in using TempData for this.
public ActionResult Action1()
{
string someMessage;
Test obj = SomeOperation();
if(obj.Valid)
{
someMessage = obj.UserName;
}
else
{
someMessage = obj.ModeratorName;
}
TempData["message"] = someMessage;
return RedirectToAction("Index");
}
public ActionResult Index()
{
ViewBag.Message = TempData["message"]
return View();
}
So is the use of TempData here correct ? I mean under best programming practices is this correct way of using TempData ?
In what real time cases should TempData be used ?
Note : I have gone through the following links
When to use TempData vs Session in ASP.Net MVC
http://www.gregshackles.com/2010/07/asp-net-mvc-do-you-know-where-your-tempdata-is/
Thanks
TempData is a bucket where you can dump data that is only needed for the following request. That is, anything you put into TempData is discarded after the next request completes. This is useful for one-time messages, such as form validation errors. The important thing to take note of here is that this applies to the next request in the session, so that request can potentially happen in a different browser window or tab.
To answer your specific question: there's no right way to use it. It's all up to usability and convenience. If it works, makes sense and others are understanding it relatively easy, it's good. In your particular case, the passing of a parameter this way is fine, but it's strange that you need to do that (code smell?). I'd rather keep a value like this in resources (if it's a resource) or in the database (if it's a persistent value). From your usage, it seems like a resource, since you're using it for the page title.
Hope this helps.
Please note that MVC 3 onwards the persistence behavior of TempData has changed, now the value in TempData is persisted until it is read, and not just for the next request.
The value of TempData persists until it is read or until the session
times out. Persisting TempData in this way enables scenarios such as
redirection, because the values in TempData are available beyond a
single request.
https://msdn.microsoft.com/en-in/library/dd394711%28v=vs.100%29.aspx
Just be aware of TempData persistence, it's a bit tricky. For example if you even simply read TempData inside the current request, it would be removed and consequently you don't have it for the next request. Instead, you can use Peek method. I would recommend reading this cool article:
MVC Tempdata , Peek and Keep confusion
I think I might already know the answer, but here goes. I want to log when certain methods are called, but I'm torn because I've been gaining performance benefits by using the OutputCache attribute with those methods. When the method is called many times, ASP.NET MVC returns the HTML from prior calls while the cache hasn't expired and it's nice and fast. But any logging commands within that method wouldn't execute.
Is there some way for me to turn on logging to record when those methods are called, without having to remove [OutputCache] and losing the performance benefits I'm getting? Would an attribute-based logging mechanism somehow work even though the [OutputCache] attribute basically short-circuits method execution when prior output has been cached?
Thanks,
Jeff
You could use the Application_BeginRequest and Application_EndRequest events in Global.asax to log information before and after the action is executed. Those events will fire even for controller actions decorated with the [OutputCache] attribute.
Darin Dimitrov is correct, use the Application_BeginRequest and Application_EndRequest methods.
This is how you would do it:
protected void Application_BeginRequest()
{
var routeCollection = RouteTable.Routes;
var routeData = routeCollection.GetRouteData(new HttpContextWrapper(HttpContext.Current));
// Log
Debug.WriteLine("Invoking " + routeData.Values["Controller"] + "::" + routeData.Values["Action"]);
}
protected void Application_EndRequest()
{
// Log
Debug.WriteLine("EndRequest");
}
Those methods will get called regardless if the version is served from the cache or not.
I am running into an issue with ASP.NET MVC where it is forcing the user to log back in after about 20 mins of inactivity.
I am using Forms Authentication and have increased the time-out in the config file as:
<authentication mode="Forms">
<forms loginUrl="~/Account/LogOn" timeout="9999999" />
</authentication>
I am also setting the session time-out in the config file as:
<sessionState timeout="120"></sessionState>
I am basing this off of Rockford Lhotka's CSLA ASP.NET MVC example and have the following in my global.asax:
protected void Application_AcquireRequestState(object sender, EventArgs e)
{
if (HttpContext.Current.Handler is IRequiresSessionState)
{
if (Csla.ApplicationContext.AuthenticationType == "Windows")
return;
System.Security.Principal.IPrincipal principal;
try
{
principal = (System.Security.Principal.IPrincipal)
HttpContext.Current.Session[MyMembershipProvider.SESSION_KEY];
}
catch
{
principal = null;
}
if (principal == null)
{
if (this.User.Identity.IsAuthenticated && this.User.Identity is FormsIdentity)
{
// no principal in session, but ASP.NET token
// still valid - so sign out ASP.NET
FormsAuthentication.SignOut();
this.Response.Redirect(this.Request.Url.PathAndQuery);
}
// didn't get a principal from Session, so
// set it to an unauthenticted PTPrincipal
BusinessPrincipal.Logout();
}
else
{
// use the principal from Session
Csla.ApplicationContext.User = principal;
}
}
}
From what I can tell it should ONLY time-out after 120 minutes of inactivity ... but for some reason it always seems to time-out after 20 minutes of inactivity. I have know idea why this is happening, any ideas?
I am toying with the idea of just dumping Forms Authentication and handling it myself via Session, but I'm afraid I would lose functionality like [Authorize] attributes and so on. Trying not to go down this path.
Is it possible to store my custom principal object as a cookie? I just don't want to have to authenticate/authorize a user for every single page or action.
I'm losing hair ... rapidly! =)
Mixing concerns of FormsAuthentication with SessionState is just a bad idea on many levels, as you are noticing from the answers you are getting.
If the information describing your custom principal is small, I would suggest storing it in the UserData member of the forms ticket. That is what it is there for.
Then your custom data, which is only valid with a valid ticket, is stored with the ticket.
Many problems solved and mucho code obviated.
Here is a helper class that can help you with your ticket.
CAVEAT: In practice the max http cookie size is just shy of the official 4k limit and Encryption cuts that in half approximately.
If you can ensure that your ticket, including principal data will fit into <2k you should be good to go. Creating a custom serialization for your principal can help, e.g. name=value pairs works great if your data will cooperate.
Good luck.
Handling it via Session may not be enough. Because it could be IIS recycling your application, therefor causing all the sessions to be abandoned.
See
[recycling] [iis]
[recycle] [iis]
Hopefully you got this solved by now, but in case somebody else comes along with the same issues, I've been responsible for debugging some code written using the same template, and here are a few thoughts:
1) The forms ticket has a timeout encoded into its value. Most of the example code out there hard-codes this timeout instead of pulling from the forms auth configuration, so if you're just looking at your web.config everything can look fine but your custom security code is ignoring the web.config value. Look through your code for "new FormsAuthenticationTicket" and see what you are doing for the expiration time.
2) The forms cookie has a timeout set in its cookie value. Some of the example code out there hard-codes this timeout. Look and see if you are setting cookie.Expires on your security cookie. (Custom auth tends to hand-build more code here than you would expect because the FormsAuthentication methods don't expose the make-a-cookie-with-userdata method, and you generally want to use userdata to store a bit of info like roles in)
3) Some clients will not set a cookie on response redirect. And sometimes even if they do, you'll get back a cookie other than the one you set. For example, if you have changed the app path or domain at any point, it's possible for the user to have two valid cookies, and you're only clearing one when you try to log them back in here. Now, this code basically reads "The user has some session info, and was logged in, but their session didn't contain the principal I expected it to, so I redirect them to login again." Well, if they don't listen to your auth cookie, or have an auth cookie you don't expect (maybe you changed your domain or path values at some point and they have a /oldpath cookie still set), this can infinite loop. I recommend nuking the session server-side as soon as you find out that it doesn't have the data you want: Session.Clear() - this leaves you less likely to end up in this situation after a redirect. (From a recover-server-side-without-trusting-the-client-to-behave perspective, it's actually slightly safer to go ahead and reconstruct the principal object and put it into the session, but I can see how this would be less secure.)
It's also safer to just do a Server.Transfer to the login page rather than relying on a cookie-changing redirect to work right. If you do end up in a redirect loop, server.transfer is guaranteed to end it.
Are you using the membership provider for authorization also? If so you may want to look at the userIsOnlineTimeWindow attribute. The default for this is also 20 minutes.
According to various articles (e.g. here and here) attribute results on ASP.NET MVC Actions may be cached and not executed again when a controller action is called.
That behavior is not desired in my case (e.g. I have an authorization system based on my own attributes and IPs, role checks that need to execute every time, and other things).
How can I prevent ASP.NET MVC from caching my attributes/attribute execution results and guarantee that they are executed every time?
Look at the source code for the AuthorizeAttribute (on Codeplex or via Reflector) to see how it goes about turning off caching for authorized pages. I refactored it into a separate method on my custom authorization attribute which derives from AuthorizeAttribute.
protected void CacheValidateHandler( HttpContext context, object data, ref HttpValidationStatus validationStatus )
{
validationStatus = OnCacheAuthorization( new HttpContextWrapper( context ) );
}
protected void SetCachePolicy( AuthorizationContext filterContext )
{
// ** IMPORTANT **
// Since we're performing authorization at the action level, the authorization code runs
// after the output caching module. In the worst case this could allow an authorized user
// to cause the page to be cached, then an unauthorized user would later be served the
// cached page. We work around this by telling proxies not to cache the sensitive page,
// then we hook our custom authorization code into the caching mechanism so that we have
// the final say on whether a page should be served from the cache.
HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
cachePolicy.SetProxyMaxAge( new TimeSpan( 0 ) );
cachePolicy.AddValidationCallback( CacheValidateHandler, null /* data */);
}
I just finished a spirited discussion with Craig Stuntz (the author of the first article you listed).
I ended up using an AuthorizeAttribute with AuthorizeCore to guarantee that authorization is called even in the event the page is cached.
We have a requirement to have our ASP.NET MVC websites be automatically closed down by a remote notification (change in database value). Where would be the best place to implement this?
Base Controller Class
Global.asax
Custom attribute
Other
Update
Lots of suggestions to use app_offline but this scenario will be happening daily and will be purely initiated by the database so I would rather have the application take the initiative rather than something external push the file in.
Also, I will probably need to redirect the users to a holding page (preferably an MVC controller method to keep everything consistent). I'm leaning more towards catching it in my BaseController and having that handle it
There's a standard way of "gracefully" terminating ASP.NET 2.0 webapp - just drop a App_Offline.htm to the root directory of your application. See this.
I would go with Global.asax Application_BeginRequest if you have to do it programmatically.
You could Response.Redirect the page to "Offline.aspx" which can retrieve a message from the database or whatever you need. Of course you'd have to look at the request to see if it was trying to get to "Offline.aspx" otherwise you'd end up in an infinite loop.
Or maybe all your applications can be redirected to a single website which would remove most the complication.
I'm going to answer this myself as I did it a different way but thanks to everyone for their responses.
What I ended up doing is overriding OnActionExecuting() in my BaseController class (which all my controllers derived from).
In this method I check the database (using a 1 minute cache) and if the website is closed I load up a view to display a closed message. Code shown below
Protected Overrides Sub OnActionExecuting(ByVal filterContext As System.Web.Mvc.ActionExecutingContext)
MyBase.OnActionExecuting(filterContext)
Dim _cfgService = ObjectFactory.GetInstance(Of IConfigService)()
If _cfgService.DynamicConfig.WebSiteClosed Then
filterContext.Result = ErrorHandler(_cfgService.DynamicConfig.WebSiteClosedTitle, _
_cfgService.DynamicConfig.WebSiteClosedMessage)
End If
End Sub
Handling this type of behavior in the Global.asax file sounds like the best solution and redirecting to a static "ofline/closed" page. Handle the request on the Application_BeginRequest method, check to see the the site is active, if it let it continue, if it is not online Response.Redirect the request to the static page.
protected void Application_BeginRequest(object sender, EventArgs e)
{
string redirectURL = "~/Offline.aspx"; //some static page
bool isOnline = false; //SQL Call, config value
if (!isOnline && !string.IsNullOrEmpty(redirectURL))
{
Response.RedirectLocation = redirectURL;
Response.End();
}
}
Sorry, don't know about ASP.NET, but in case helpful:
We have a single APPLICATION.ASP page for our site (CMS / Database merge type stuff); this is possibly not common and therefore may therefore restrict usefulness, but could perhaps be implemented by an INCLUDE at the top of all ASPX files
We rename APPLICATION.ASP to TEST.ASP and copy HOLDING_PAGE.ASP to APPLICATION.ASP
The HOLDING_PAGE.ASP is present in the WWW folder, so always ready-and-available. It contains a "Site not available" message etc. and is self contained for all CSS (no include files, no DB access). Only exception is the company logo (which is external to that file, obviously)
This method prevents all access to the site, is not dependant on having a working DB connection, and allows us to change anything on the site without interfering with the holding page (apart from Company Logo, but changing that is likely to be benign)
We can still access the site, internally, using TEST.ASP - so we can test any new rollout features before removing the holding page and putting the site live. If you want to prevent anonymous use of TEST.ASP then deny anonymous permission.
Remove holding page is: Delete APPLICATION.ASP (i.e. the holding page) and Rename TEST.ASP to APPLICATION.ASP
We also have a database flag that causes the normal APPLICATION.ASP page to show a holding page - which we can use whilst doing more minor changes.