Cookieless ASP.Net sessions for static pages - asp.net

I'm using ASP.Net cookieless sessions so that the session ID for the application is tracked by placing it in the URL via a 302 redirect, for example if the user were to access the below URL
http://yourserver/folder/default.aspx
They would then be redirect to a URL similar to the following which would then proceed to serve up the actual page content
http://yourserver/folder/(S(849799d1-7ec0-41dc-962d-a77e1b958b99))/default.aspx
The problem I have is that the entry point for the application is actually a static page (e.g. one with a .html extension), and ASP.Net is not issuing a session ID & redirecting the user for this page. This therefore means that links to ASP.Net hosted content (e.g. links, iframes etc...) each result in a new session ID being created for each of these links. I cannot easily change the pages extension for compatability reasons (although this does fix the problem).
How can I prompt ASP.Net to create a session for my page? I've tried adding an explicit handler mapping to ensure that the page is being handled by the ASP.Net modules, however this has no impact - while debugging I can see that a SessionIDManager instance is being created for this page (implying that the ASP.Net is already handling this page via the integrated pipeline regardless of my handler mapping), however ASP.Net is still not creating a session for this page.
I am using IIS 7, however this also needs to work on IIS 6 (with expicit handler mappings) and IIS 8.

I have discovered from experimentation and decompiling the ASP.Net source that the SessionStateModule decides whether or not to create a session for a request based on whether or not the IHttpHandler for the request implements IRequiresSessionState or IReadOnlySessionState. If neither of those are true then its all down to whether or not someone has used SetSessionStateBehavior to set the session state behaviour to either Required or ReadOnly.
By default the static file handler does not do any of these and so the session is not created for static files - to ensure that the session state behaviour is set for the static files that require session state I simply set the session state behaviour during the BeginRequest event for the application
// In Global.asax.cs
void Application_BeginRequest(object sender, EventArgs e)
{
HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Default);
}
I'm sure there are better ways, but this worked for me.

Related

Tabs opened via Hyperlinks in Excel/Word not recognizing session cookies

I have an ASP.Net application with authentication using Cookie session variables. Once the user logs in, they can open new browser tabs for the same application and these are logged in automatically as the session cookie is present.
Clicking on a hyperlink on another web page pointing to a specific page within the application also works fine - there is no login required as the user is already logged in.
However, when a hyperlink to the application is in a Word/Excel document, this link does not open the page directly and gets bounced to the Login page instead. If I copy/paste the Url from Word/Excel and paste it in the Url bar on the browser, it works fine.
Any explanation to this behaviour? Does the browser open a isolated session when a link is clicked in Word/Excel?
Edit: It also seems Word/Excel perform their own check before opening a browser tab. If I use a non-existent link, it doesn't open the tab.
We ran into this at my place of work a while back, and found that like you mentioned, MS Office applications indeed do some mysterious stuff behind the scenes. Details on what it actually does are in this article: https://learn.microsoft.com/en-us/office/troubleshoot/office-suite-issues/click-hyperlink-to-sso-website
Toward the bottom of that article, they suggest a workaround involving a meta refresh, which is what worked for us. In our case, we added a method to our request pipeline that checks for a Microsoft product in the User-Agent header. If found, it sends a meta refresh that triggers the browser to use an existing session rather than trying to start a new session (which is why you're being redirected to a logon page). Here's more or less the code:
private static string MSUserAgentsRegex = #"[^\w](Word|Excel|PowerPoint|ms-office)([^\w]|\z)";
protected void Application_OnPostAuthenticateRequest(object sender, EventArgs e)
{
if (!Request.IsAuthenticated)
{
if (System.Text.RegularExpressions.Regex.IsMatch(Request.UserAgent, MSUserAgentsRegex))
{
Response.Write("<html><head><meta http-equiv='refresh' content='0'/></head><body></body></html>");
Response.End();
}
}
}

Why is session not created if page is cached?

Question: Why is there no session created (and session cookie) when an aspx page requested is pulled from cache?
Background Information
I've made quite a few google searches, but I can't find anything that indicates that this is the intended behavior. Our desired behavior is that a new session/cookie are always generated regardless of whether the page requested is pulled from cache.
We are caching pages using the following code. (.NET 3.5, IIS 7.5)
Response.Cache.SetExpires(DateTime.Now.AddMonths(1));
Response.Cache.SetCacheability(HttpCacheability.Server);
Response.Cache.SetVaryByCustom("IsLoggedIn");
Response.Cache.VaryByParams["*"] = true;
Response.Cache.SetValidUntilExpires(true);
Response.AddCacheItemDependency("Pages");
Any relevant information would be greatly appreciated.
In order to understand why an output-cached request will not create a session, you need to understand a little bit about the ASP.NET Application Life Cycle, the events . The basics are that are a defined series of HttpApplication events that may fire during each request. These events are typically subscribed to using event handlers by either HttpModule implementations or a Global.asax file.
Not every application event fires on every request, but the events that do fire will always fire in the defined order. The order of events that fire on IIS 7
1. BeginRequest
2. AuthenticateRequest
PostAuthenticateRequest
3. AuthorizeRequest
PostAuthorizeRequest
4. ResolveRequestCache
PostResolveRequestCache
5. MapRequestHandler (Integrated Mode Only)
PostMapRequestHandler
6. AcquireRequestState
PostAcquireRequestState
7. PreRequestHandlerExecute
<--- the IHttpHandler.ProcessRequest() method is called here
PostRequestHandlerExecute
8. ReleaseRequestState
PostReleaseRequestState
9. UpdateRequestCache
PostUpdateRequestCache
10. LogRequest (Integrated Mode Only)
PostLogRequest (Integrated Mode Only)
11. EndRequest
12. PreSendRequestHeaders
13. PreSendRequestContent
How an IHttpModule Works
The IHttpModule interface looks like:
public interface IHttpModule
{
void Init(HttpApplication context);
void Dispose();
}
The Init() method is used to subscribes event handlers to the application events and the Dispose() method cleans up after the module when the application is done with it.
How ASP.NET Sessions Work
System.Web defines an IHttpModule implementation named System.Web.SessionState.SessionStateModule. If the session is not disabled in the web.config, the following event handlers get wired up:
// app is the current HttpApplication ;
app.AddOnAcquireRequestStateAsync(this.BeginAcquireState, this.EndAcquireState);
app.ReleaseRequestState += this.OnReleaseState;
app.EndRequest += this.OnEndRequest;
Sessions work differently depending on what session mode is running but the key thing to understand is that sessions are retrieved and created in the SessionStateModule.BeginAcquireState method and that method is wired up asynchronously to the AcquireRequestState event.
How Output Caching Works
System.Web defines an internal IHttpModule implementation named System.Web.Caching.OutputCacheModule. The Init() method looks like:
void IHttpModule.Init(HttpApplication app)
{
if (RuntimeConfig.GetAppConfig().OutputCache.EnableOutputCache)
{
app.ResolveRequestCache += new EventHandler(this.OnEnter);
app.UpdateRequestCache += new EventHandler(this.OnLeave);
}
}
The OnEnter method evaluates the cache parameters and looks for a cached response that matches the request. The OnLeave method caches cacheable responses. The one thing you need to know is that if the OnEnter method is successful in retrieving a cached response, it calls the CompleteRequest() method on the HttpApplication instance. The documentation for this method reads: "Causes ASP.NET to bypass all events and filtering in the HTTP pipeline chain of execution and directly execute the EndRequest event". So, any events that occur between the time that CompleteRequest() is called and the EndRequest() event are skipped.
Why is session not created if page is cached?
The SessionStateModule subscribes SessionStateModule.BeginAcquireState to the application's AcquireRequestState event, so that is where sessions are created and retrieved.
The OutputCacheModule subscribes OutputCacheModule.OnEnter to the application's ResolveRequestCache event, so that is where cached responses are retrieved and where CompleteRequest() gets called. That means that the following events will never fire for a cached request: MapRequestHandler/PostMapRequestHandler; AcquireRequestState/PostAcquireRequestState; PreRequestHandlerExecute; IHttpHandler.ProcessRequest; PostRequestHandlerExecute; ReleaseRequestState/PostReleaseRequestState; UpdateRequestCache/PostUpdateRequestCache; and, LogRequest/PostLogRequest.
If a cached response is retrieved, then HttpApplication.CompleteRequest() is called, the AcquireRequestState event never fires, and the session is never created.
What to do about it?
Whether disabling output cache on pages in favor of controls is a viable solution depends on: (1) whether the code that uses the session is in the controls or in the pages; (2) how much load your application instance needs to be able to support; and, (3) what other caching mechanisms exist in either the application or its supporting infrastructure. Depending on your infrastructure, there may be other factors to consider. For example, ASP.NET sessions can perform and behave very differently depending on the session state mode and on whether the environment is load balanced or not. Also, if you happened to be running an HTTP accelerator cache like Varnish (for example), enabling sessions on a previously output-cached ASP.NET paged might change the behavior from omitting a session to attaching a stale session that belongs to a different user. This is just a hypothetical example, but the point is that there may be additional factors to consider when making these kinds of decisions.
Apparently this is default .NET behavior. If you create a new app, enable session, cache an aspx page, and then have a new user pull the cached version of that page, they will be given no session/cookie.
As a workaround...I think we'll just disable page outputcache and use more aggressive control caching.

AJAX Partial Rendering issues for the default page in IIS 7 when using custom http module

The problem
When I try to make a AJAX partial update request (using the UpdatePanel control) from the default page of an IIS7 web site, it fails- instead of returning the html to be updated, it returns the entire page, which then causes the MS AJAX Javascript to throw a parsing shit-fit.
The suspected cause
I have narrowed the cause down to two issues- making an AJAX request to the default page when I have a certain custom http module registered. A partial rendering request to http://localhost will fail, but a partial rendering request to http://localhost/default.aspx will work fine.
Also, If i remove the following line in my custom HttpModule:
_application.PreRequestHandlerExecute += OnPreRequestHandlerExecute;
The AJAX partial render will work correctly. Wierd huh?
Another wierd thing...
If I look at trace.axd, I can see that when a partial rendering request fails, two POST requests are logged for the one partial rendering request- one where the default.aspx page executes successfully (trace information such as page_load is logged) but no content is produced and a second that doesn't seem to actually execute (no trace information is logged) but produces content (HTTP_CONTENT_LENGTH is greater than 0).
Please help!
If anyone with a good knowledge of HTTP modules or the MS AJAX Http module could explain why this is occuring I would be very grateful. As it is, the obvious work arround is to just redirect to default.aspx if the request url is "/" but I would really like to understand why this is occurring.
First of all PreRequestHandlerExecute is exactly before HTTP handler executes.
Second for hosting websites with HttpModules under IIS7 it is better that we run website in integrated pipeline mode, and also we have to move HttpModules tag in web.config to system.webServer module section.
If for example you change the handler in PreRequestHandlerExecute like this:
void context_PreRequestHandlerExecute(object sender, EventArgs e)
{
HttpApplication application = (HttpApplication)sender;
HttpContext context = application.Context;
if( something-happened )
context.Handler = null;
}
The result would be exactly as you said.
Setting handler to any thing else but its default, means that ASP.Net is not responsible for current request.
Note that each request only can have one HttpHandler.

Can I get information about the IIS7 virtual directory from Application_Start?

I have 3 IIS7 virtual directories which point to the same physical directory. Each one has a unique host headers bound to it and each one runs in its own app pool. Ultimately, 3 instances of the same ASP.NET application.
In the Application_Start event handler of global.asax I would like to identify which instance of the application is running (to conditionally execute some code). Since the Request object is not available, I cannot interrogate the current URL so I would like to interrogate the binding information of the current virtual directory?
Since the host header binding is unique for each site, it would allow me to identify which application instance is starting up. Does anyone know how to do this or have a better suggestion?
When a request is made, and just prior to the HttpApplication instance being created, ASP.NET initializes core objects such as HttpContext, HttpRequest and HttpResponse which means they will exist when you get to the Application_Start event in Global.asax. Thus, in Application_Start, you can get the requesting url like so:
var url = this.Context.Request.Url;

Readonly access to session in a web service call?

We have a .net asmx web service that gets called from javascript (using ASP.Net AJAX), and requires access to Session.
[WebMethod(true)]
public string DoSomethingOnTheServer() { }
We're running into the problem of session being locked on a read/write request. Is there any way to mark a web service method as requiring read-only access to Session?
Thanks!
This is a really old thread, but i stumbled on it in my search for an answer to the same question.
I found the answer else where, and will leave it here for other internets in my place:
In Global.asax you can specify for each request, what access the request should have to the session object, and thereby if it should block or not.
private void Application_BeginRequest(object sender, EventArgs e)
{
// This will set the session to read only for asmx services
// This will make the asmx services non blocking for other requests as it doesnt lock the session object
if (Context.Request.Path.Contains(".asmx/"))
{
Context.SetSessionStateBehavior(SessionStateBehavior.ReadOnly);
}
}
This way asmx services always only have read only access to the session and will not block other requests
This http://msdn.microsoft.com/en-us/library/aa480509.aspx page seems to suggest that the answer is "no" - you cannot mark a WebSerivce as having EnableSessionState=ReadOnly.
If you are making simultaneous Web service calls from the same process, the requests will be serialized at the server so that only one will execute at any one time. Unlike .ASPX pages that have support for read-only access to the HttpSessionState object, which allows for simultaneous processing of multiple requests, there is no such capability with ASP.NET Web services. All Web method calls with sessions enabled have read/write access and will be serialized within each session.
Warning: That article is old (2002).
According to the MSDN documentation of the WebMethod Attribute there are several possible properties, so I'm not sure what the 'true' value in your WebMethod Attribute going to do.
Have you tried:
[WebMethod(EnableSession=true)]
According to this document that should give you full access to the session.

Resources