Setting cache control to private for ALL items - asp.net

I'm about to include this into our build:
void Application_PreSendRequestHeaders(Object sender, EventArgs e)
{
// Cache only on the server and the client (if SSL is disabled)
// This doesn't determine what should be cached, only where
// cacheable items are allowed to be stored.
Response.Cache.SetCacheability(HttpCacheability.Private);
}
First off, are my comments in the code correct?
Second, is this an OK action to take? Is it fine to set a cache-control header for all requests? Most my reading, people set cache headers per directory.
I assume this should probably be done in IIS, but we are running in azure so its a little trickier making IIS changes (have to automate it).

Related

Remove query strings from static resources

My website is running on ASP.NET platform and recently i test my website on pingdom and i found the below error.
Resources with a "?" in the URL are not cached by some proxy caching
servers. Remove the query string and encode the parameters into the
URL for the following resources:
https://projectsdeal.co.uk/ScriptResource.axd?d ...
63Nawdr4rAt1lvT7c_zyBEkV9INg0&t=ffffffffe3663df5
https://projectsdeal.co.uk/ScriptResource.axd?d ...
JGTlZFM0WRegQM9wdaZV3fQWMKwg2&t=ffffffffe3663df5
Simple leave it as it is (its not an error !) - you can not remove this query string from resource because this is the id on how to load that resource from asp.net
The message that you get is actually talk for a proxy caching servers - what is a proxy caching server ? a middle computer that cache pages of your site, not the actually client computer - that can hold in cache that page and not bring slower your site in general.
So your client can hold that resource on cache if you set them correctly, and from what I see asp.net take care correctly and you resource are cached just fine - see this screen shot.
Now if you wish to add even more aggressive cache you can use the global.asax and do something like
protected void Application_BeginRequest(Object sender, EventArgs e)
{
string cTheFile = HttpContext.Current.Request.Path;
if (cTheFile.EndsWith("WebResource.axd", StringComparison.InvariantCultureIgnoreCase))
{
JustSetSomeCache(app);
}
}
private static void JustSetSomeCache(HttpApplication app)
{
app.Response.Cache.AppendCacheExtension("post-check=900, pre-check=3600");
app.Response.Cache.SetExpires(DateTime.UtcNow.AddHours(32));
app.Response.Cache.SetMaxAge(new TimeSpan(32, 0, 0));
app.Response.Cache.SetCacheability(HttpCacheability.Public);
app.Response.AppendHeader("Vary", "Accept-Encoding");
}
What is the different ? The second cache is not check the server at all for file change as the asp.net do, you can gain one webserver call.

Asp.net session never expires when using SignalR and transport mode long polling

We have a web application that uses SignalR for its notification mechanism.The problem is when we are browsing our web application using IE ,SignalR uses Long Polling as its transport type thus sends back requests to our web server therefore Session never expires no matter how long the browser is idle.
We were thinking that maybe we could catch the requests in Global.asax and see if they were from SingalR and set the session timeout to the remaining time (Which I don't think it's a straightforward solution).
Is there any other solution the we are missing ?
The workaround I am currently using is an IHttpModule to check if the request is a Signalr request, if so remove the authentication cookie, this will prevent the ASP.net session timeout from being reset, so if your Session Timeout is 20min and the only requests are Signalr the users session will still timeout and the user will have to login again.
public class SignalRCookieBypassModule : IHttpModule
{
public void Init(HttpApplication application)
{
application.PreSendRequestHeaders += OnPreSendRequestHeaders;
}
private bool IsSignalrRequest(string path)
{
return path.IndexOf("/signalr/", StringComparison.OrdinalIgnoreCase) > -1;
}
protected void OnPreSendRequestHeaders(object sender, EventArgs e)
{
var httpContext = ((HttpApplication)sender).Context;
if (IsSignalrRequest(httpContext.Request.Path))
{
// Remove auth cooke to avoid sliding expiration renew
httpContext.Response.Cookies.Remove(DefaultAuthenticationTypes.ApplicationCookie);
}
}
public void Dispose()
{
}
}
I feel this is a real hack solution so would love so other ideas to prevent session timeout renew when data is pushed to the client from the server, or a when javascript client polls an endpoint for data.
If you take a look at the description of the SignalR protocol I wrote a while ago you will find this:
» ping – pings the server
...
Remarks: The ping request is not really a “connection management request”. The sole purpose of this request is to keep the ASP.NET session alive. It is only sent by the the JavaScript client.
So, I guess the ping request is doing its job.
I here post #Simon Mourier's commented solution, with his approval, as a CW answer, as I find the suggested approach the most appropriate and less intrusive, as it just disables the Session for SignalR requests.
A positive side effect is that the request will be processed faster as the Session object doesn't need to be initiated and loaded.
It still uses a IHttpModule for the work, and the preferable place is likely the AcquireRequestState event (not personally tested yet though), or at an event raised earlier, before making use of the Session object.
Do note using this approach that one might need to test that the Session object is available before access any of its members or stored objects.
public class SignalRSessionBypassModule : IHttpModule
{
public void Init(HttpApplication application)
{
application.AcquireRequestState += OnAcquireRequestState;
}
private bool IsSignalrRequest(string path)
{
return path.IndexOf("/signalr/", StringComparison.OrdinalIgnoreCase) > -1;
}
protected void AcquireRequestState(object sender, EventArgs e)
{
var httpContext = ((HttpApplication)sender).Context;
if (IsSignalrRequest(httpContext.Request.Path))
{
// Run request with Session disabled
httpContext.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Disabled);
}
}
public void Dispose()
{
}
}
Here is another completely different approach, simple, yet quite efficient.
Instead of relying on Session/Auth cookies to decide whether a user has timed out, use the Cache object. This have more or less no side effects and work just like if the user simply logged out.
By simply add this small snippet somewhere in the beginning of your web app code, where of course SignalR don't go, you will be able to check if the cache item is there and reinitiate it (with the same expiration time as the Session timeout is set), and if not, just do a logout and remove cookies/session variables.
if (Request.IsAuthenticated) {
if (Cache[Context.User.Identity.Name] == null) {
// Call you logout method here...,
// or just:
// - Sign out from auth;
// - Delete auth cookie
// - Remove all session vars
} else {
// Reinitiate the cache item
Cache.Insert(Context.User.Identity.Name,
"a to you usable value",
null,
DateTime.Now.AddMinutes(Session.Timeout),
Cache.NoSlidingExpiration,
CacheItemPriority.Default,
null
);
}
And within your user login method, you just add this, to create the cache item for the first time
// Insert the cache item
Cache.Insert(Context.User.Identity.Name,
"a to you usable value",
null,
DateTime.Now.AddMinutes(Session.Timeout),
Cache.NoSlidingExpiration,
CacheItemPriority.Default,
null
);
It's more stable and maintainable -in my view- to have your own "session like timeout" . Set your .NET session timeout to infinity since you'll not be using it and then create a global JavaScript counter (in your layout or master page) to track the time passing while the browser is idle (obviously setTimeout or setInterval every few seconds would do the trick). Make sure to have the counter reset on every web request (that should happen automatically since all JavaScript variables would reset). In case you have pages that depend on web services or Web API, make sure to reset your global JavaScript counter on every call. If the counter reaches your desired timeout without being reset, that means that the session is expired and you can logout the user. With this approach you'll have full control over the session lifetime which enables you to create a logout timer popup to warn the user that the session is about to expire. SignalR would perfectly fit with this approach since the JavaScript timer would keep ticking.

How to stop request to website till cache is being built

I am working on website which stores important data in HTTPContext.Cache for fast performance. If any data is changed during the day time. we have automated process to clear and regenerate the cache. But the problem is when cache is getting generated any user trying to access the website gets error.
I am looking for a solution to this problem. Where i can stop the request to website till cache generation is not finished.
One is to put app_offline file and delete it when cache is fully refreshed.
Please do let me know if you need more information
You can use the global.asax and do something like
protected void Application_BeginRequest(Object sender, EventArgs e)
{
if(fCacheIsNotReady)
{
HttpContext.Current.Response.TrySkipIisCustomErrors = true;
HttpContext.Current.Response.Write("We not build our site, please try again in few seconds.");
HttpContext.Current.Response.StatusCode = 403;
HttpContext.Current.Response.End();
return ;
}
}
The fCacheIsNotReady can be global static parametre that is true, when you start build you cache, and false when you end,
You can also check what files are you going to stop.
I do not know how you create your cache, you may also use some lock() to hold the users until the cache is made.

ASP.NET: what's wrong with this online users counter?

I'm trying to count the amount of online users.
This is the code:
protected void Application_Start()
{
...
Application["OnlineUsers"] = 0;
}
private void Session_Start(object sender, EventArgs e)
{
Application.Lock();
Session["O"] = "OO"; // Need to have something in the session
Application["OnlineUsers"] = (int)Application["OnlineUsers"] + 1;
Application.UnLock();
}
private void Session_End(object sender, EventArgs e)
{
Application.Lock();
Application["OnlineUsers"] = (int)Application["OnlineUsers"] - 1;
Application.UnLock();
}
There are <b>#Context.ApplicationInstance.Application["OnlineUsers"].ToString()</b> users online
It kind of works, but I always have the value that is greater than the actual amount of users online.
And even worse: in Opera refreshing the page N times increases the amount of online users by N!
It is important to note here that the ASP.NET is trying to be extremely efficient storing sessions for users. If ASP.NET doesn’t have a reason to remember who you are, it won’t.
When we request a page first time, a session object will be created and its session identifier will be sent to web-browser so browser can store session identifier in cookie (for identiity of request). If that page is again submitted/postedback then the same session identifier from the cooike will be available to the app-server and it assume that this is an old-request. But in your case (you are refereshing a page), it means web-browser issue a fresh request (and of-course the request type will be GET) without sending cookies. So, it is better to redirect the user to a specific page on first request.
The Session_End event handle will be called when Session get timeout (default value is 20 minutes) even after that client (browser) is closed (or ends the session).
I know its not quite what you are asking but you can query the PerformanceCounter on IIS for this info
(Razor Example)
#using System.Diagnostics
#{
var perf = new PerformanceCounter("ASP.NET", "State Server Sessions Active");
}
<h2>About</h2>
<p>
#perf.NextValue()
</p>
I didn't check but your access to this might need a windows/service account in your app pool.
You can also Increment and Decrement you own Performance counters and make them available to system admins via the tools they use to monitor Websites etc.
The SqlMembershipProvider has a facility for counting logged on users, which would mean you probably already have the data sitting in your database if you are using it to manage your forms authentication.
You could also consider having your pages emit an ajax pulse every 'period of time' and count that. or have some applet, silverlight, flash etc doing the same.

How can I generate a 404 response from an IHttpModule configured in the global web.config?

I'm trying to generate a 404 response for certain requests on all sites on a server based on the HttpRequest.UserAgent.
I've configured an IHttpModule in the server's global web.config containing the code below:
private void Application_BeginRequest(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
if (isBot(context.Request.UserAgent))
{
System.Diagnostics.EventLog.WriteEntry(
"Application",
"\nBotRequestChecker -- request 404d\n" + "Url: " +
context.Request.Url + "\nUserAgent: " + context.Request.UserAgent,
EventLogEntryType.Information);
context.Response.StatusCode = 404;
context.Response.StatusDescription = "Not Found";
context.ApplicationInstance.CompleteRequest();
}
}
Setting the User-Agent in a browser and visiting a page results in an entry in the event log, but the page is also returned, without a 404 status.
Any thoughts on what I'm missing?
Update:
The IHttpModule does seem to work if I add it to a single site (in the site's web.config), just not for all sites.
Update 2:
The IHttpModule only works on a single site on an IIS7 server, not on IIS6.
Make sure that you've subscribed to the the BeginRequest event in your module's IHttpModule.Init() implementation. The events don't get auto-wired in IHttpModule implementations the way same they do in Global.asax.
I also missed the bit about the global web.config at first.
On a 64-bit server, you'll need to make sure you make the changes in both the 32- and 64-bit configurations (depending on the bitness that your sites are running in) in all ASP.NET versions you need to support:
%windows%\Microsoft.NET\Framework\v2.0.50727\CONFIG\web.config
%windows%\Microsoft.NET\Framework64\v2.0.50727\CONFIG\web.config
%windows%\Microsoft.NET\Framework\v4.0.30319\CONFIG\web.config
%windows%\Microsoft.NET\Framework64\v4.0.30319\CONFIG\web.config
If you're targeting both IIS6 and IIS7, you'll need to make sure the module is referenced in both the <system.web>/<httpModules> element for IIS6 and the <system.webServer>/<modules> element for IIS7.

Resources