I am storing a single integer value in HttpContext.Cache with an absolute expiration time of 5 minutes from now. However, after waiting 6 minutes (or longer), the integer value is still in the Cache (i.e. it's never removed even though the absolute expiration has passed). Here is the code I am using:
public void UpdateCountFor(string remoteIp)
{
// only returns true the first time its run
// after that the value is still in the Cache
// even after the absolute expiration has passed
// so after that this keeps returning false
if (HttpContext.Current.Cache[remoteIp] == null)
{
// nothing for this ip in the cache so add the ip as a key with a value of 1
var expireDate = DateTime.Now.AddMinutes(5);
// I also tried:
// var expireDate = DateTime.UtcNow.AddMinutes(5);
// and that did not work either.
HttpContext.Current.Cache.Insert(remoteIp, 1, null, expireDate, Cache.NoSlidingExpiration, CacheItemPriority.Default, null);
}
else
{
// increment the existing value
HttpContext.Current.Cache[remoteIp] = ((int)HttpContext.Current.Cache[remoteIp]) + 1;
}
}
The first time I run UpdateCountFor("127.0.0.1") it inserts 1 into the cache with key "127.0.0.1" and an absolute expiration of 5 minutes from now as expected. Every subsequent run then increments the value in the cache. However, after waiting 10 minutes it continues to increment the value in the Cache. The value never expires and never gets removed from the Cache. Why is that?
It's my understanding that an absolute expiration time means the item will get removed approximately at that time. Am I doing something wrong? Am I misunderstanding something?
I'm expecting the value to be removed from the Cache after 5 minutes time, however it stays in there until I rebuild the project.
This is all running on .NET 4.0 on my local machine.
It turns out that this line:
HttpContext.Current.Cache[remoteIp] = ((int)HttpContext.Current.Cache[remoteIp]) + 1;
removes the previous value and re-inserts the value with NO absolute or sliding expiration time. In order to get around this I had to create a helper class and use it like so:
public class IncrementingCacheCounter
{
public int Count;
public DateTime ExpireDate;
}
public void UpdateCountFor(string remoteIp)
{
IncrementingCacheCounter counter = null;
if (HttpContext.Current.Cache[remoteIp] == null)
{
var expireDate = DateTime.Now.AddMinutes(5);
counter = new IncrementingCacheCounter { Count = 1, ExpireDate = expireDate };
}
else
{
counter = (IncrementingCacheCounter)HttpContext.Current.Cache[remoteIp];
counter.Count++;
}
HttpContext.Current.Cache.Insert(remoteIp, counter, null, counter.ExpireDate, Cache.NoSlidingExpiration, CacheItemPriority.Default, null);
}
This will get around the issue and let the counter properly expire at the absolute time while still enabling updates to it.
Try using DateTime.UtcNow to calculate your timeout period instead of datetime.Now . You may be running into the issue described below:
absoluteExpiration Type:
System.DateTime The time at which the
inserted object expires and is removed
from the cache. To avoid possible
issues with local time such as changes
from standard time to daylight saving
time, use UtcNow rather than Now for
this parameter value. If you are using
absolute expiration, the
slidingExpiration parameter must be
NoSlidingExpiration.
There's a simpler answer than what smoak posted. Using that example as a starting point, the updated code below works and doesn't require a re-insert. The reason this works is because classes are reference types. Thus, when you update the counter inside the class instance it doesn't cause the cache to trigger an update.
public class IncrementingCacheCounter
{
public int Count;
}
public void UpdateCountFor(string remoteIp)
{
IncrementingCacheCounter counter = null;
if (HttpContext.Current.Cache[remoteIp] == null)
{
counter = new IncrementingCacheCounter { Count = 1};
HttpContext.Current.Cache.Insert(remoteIp, counter, null, DateTime.Now.AddMinutes(5), Cache.NoSlidingExpiration, CacheItemPriority.Default, null);
}
else
{
counter = (IncrementingCacheCounter)HttpContext.Current.Cache[remoteIp];
counter.Count++;
}
}
Related
I want to create an application wide feed on my ASP.net 3.5 web site using the application cache. The data that I am using to populate the cache is slow to obtain, maybe up to 10 seconds (from a remote server's data feed). My question/confusion is, what is the best way to structure the cache management.
private const string CacheKey = "MyCachedString";
private static string lockString = "";
public string GetCachedString()
{
string data = (string)Cache[CacheKey];
string newData = "";
if (data == null)
{
// A - Should this method call go here?
newData = SlowResourceMethod();
lock (lockString)
{
data = (string)Cache[CacheKey];
if (data != null)
{
return data;
}
// B - Or here, within the lock?
newData = SlowResourceMethod();
Cache[CacheKey] = data = newData;
}
}
return data;
}
The actual method would be presented by and HttpHandler (.ashx).
If I collect the data at point 'A', I keep the lock time short, but might end up calling the external resource many times (from web pages all trying to reference the feed). If I put it at point 'B', the lock time will be long, which I am assuming is a bad thing.
What is the best approach, or is there a better pattern that I could use?
Any advice would be appreciated.
I add the comments on the code.
private const string CacheKey = "MyCachedString";
private static readonly object syncLock = new object();
public string GetCachedString()
{
string data = (string)Cache[CacheKey];
string newData = "";
// start to check if you have it on cache
if (data == null)
{
// A - Should this method call go here?
// absolut not here
// newData = SlowResourceMethod();
// we are now here and wait for someone else to make it or not
lock (syncLock)
{
// now lets see if some one else make it...
data = (string)Cache[CacheKey];
// we have it, send it
if (data != null)
{
return data;
}
// not have it, now is the time to look for it.
// B - Or here, within the lock?
newData = SlowResourceMethod();
// set it on cache
Cache[CacheKey] = data = newData;
}
}
return data;
}
Better for me is to use mutex and lock depended on the name CacheKey and not lock all resource and the non relative one. With mutex one basic simple example will be:
private const string CacheKey = "MyCachedString";
public string GetCachedString()
{
string data = (string)Cache[CacheKey];
string newData = "";
// start to check if you have it on cache
if (data == null)
{
// lock it base on resource key
// (note that not all chars are valid for name)
var mut = new Mutex(true, CacheKey);
try
{
// Wait until it is safe to enter.
// but also add 30 seconds max
mut.WaitOne(30000);
// now lets see if some one else make it...
data = (string)Cache[CacheKey];
// we have it, send it
if (data != null)
{
return data;
}
// not have it, now is the time to look for it.
// B - Or here, within the lock?
newData = SlowResourceMethod();
// set it on cache
Cache[CacheKey] = data = newData;
}
finally
{
// Release the Mutex.
mut.ReleaseMutex();
}
}
return data;
}
You can also read
Image caching issue by using files in ASP.NET
I ahve implemented brute force protection via limitation of failed login counts as here: http://madskristensen.net/post/Brute-force-protect-your-website.aspx
But i'm encountering two issues:
After certain amount of time ( in my case 2 minutes) record in cache is not expired and i'm unable to log in again. This means that when function checks the number of failed attempts, it still gets maximum allowed after this 5 minutes
cache from MSDN as I understood is single storage for application. From what i see in my application, it seems like cache is per application per IP. Why?
Any suggestions? Here's my code:
int CountOfFailedLoginAttempts()
{
if(Cache["L1|"+TextBox1.Text]==null)
{
return 0;
}
return (int) Cache["L1|" + TextBox1.Text];
}
void AddFailedAttempt()
{
if(Cache["L1|"+TextBox1.Text]==null)
{
Cache.Insert("L1|"+TextBox1.Text,1,null,System.Web.Caching.Cache.NoAbsoluteExpiration,new TimeSpan(0,2,0));
}
else
{
int tries = (int) Cache["L1|" + TextBox1.Text];
Cache["L1|" + TextBox1.Text] = tries + 1;
}
}
void ClearFailedAttemptCounter()
{
Cache.Remove("L1|" + TextBox1.Text);
}
protected void Button1_Click(object sender, EventArgs e)
{
if (CountOfFailedLoginAttempts() >= 5)
{
Label1.Text = "Login will be unavailable for 2 minutes";
}
else
{
SqlConnection con =
new SqlConnection("valid connection string");
SqlCommand cmd = new SqlCommand("Select top 1 password from users WHERE UserName=#UN", con);
cmd.CommandTimeout = 600;
cmd.Parameters.Add(new SqlParameter("UN", TextBox1.Text));
con.Open();
string res = (string) cmd.ExecuteScalar();
con.Close();
if (res == TextBox2.Text)
{
FormsAuthentication.RedirectFromLoginPage(TextBox1.Text, true);
ClearFailedAttemptCounter();
}
else
{
Label1.Text = "Wrong password. "+(5-CountOfFailedLoginAttempts()).ToString()+"more attempts and access will be suspended for 2 minutes.";
AddFailedAttempt();
}
}
}
}
You're using sliding expiration (of 2 minutes), which means that your cache item will remain while someone is still reading the value within that time. This means that your account will be blocked forever if you keep retrying every minute.
The cache is a cache, not a critical data storage. You can not count on items remaining for two full minutes, memory pressure at the server may force ASP.NET to evict items from the cache. There's also possibilities of web farms/gardens that will give you several worker processes (perhaps spread over several machine) which will all have their own cache.
Thanks for answers. As it turned out, the problem is in this line:
Cache["L1|" + TextBox1.Text] = tries + 1;
The mechanism is quite diffrent than i thought. Instead of replacing the value, it is removing the value of specified key in cache, and inserting new one, BUT with no expiration settings. Because of that it seemed like value never expires. That is relevant for both Absolute and Sliding expiry modes. I have solved problem like that:
void AddFailedAttempt()
{
if(Cache["L1|"+TextBox1.Text]==null)
{
Cache.Insert("L1|"+TextBox1.Text,1,null,System.Web.Caching.Cache.NoAbsoluteExpiration,TimeSpan.FromMinutes(2));
}
else
{
int tries = (int) Cache["L1|" + TextBox1.Text];
Cache.Remove("L1" + TextBox1.Text);
Cache.Insert("L1|" + TextBox1.Text, tries+1, null, System.Web.Caching.Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(2));
}
}
And in that way everything works properly.
I have a XtraScheduler SchedulerControl configured as the following:
private DevExpress.XtraScheduler.SchedulerControl _SchedulerControl;
public DevExpress.XtraScheduler.SchedulerControl ConvSchedulerControl
{
get
{
if (_SchedulerControl == null)
{
_SchedulerControl = new DevExpress.XtraScheduler.SchedulerControl();
_SchedulerControl.Storage = new SchedulerStorage();
_SchedulerControl.Storage.Appointments.Mappings.Subject = "StandingOrderIDString";
_SchedulerControl.Storage.Appointments.Mappings.Start = "ScheduledDate";
_SchedulerControl.Storage.Appointments.Mappings.RecurrenceInfo = "RecurrenceInfo";
_SchedulerControl.Storage.Appointments.Mappings.Type = "Type";
_SchedulerControl.Storage.Appointments.CustomFieldMappings.Add(new DevExpress.XtraScheduler.AppointmentCustomFieldMapping("Inactive", "Inactive"));
_SchedulerControl.Storage.Appointments.CustomFieldMappings.Add(new DevExpress.XtraScheduler.AppointmentCustomFieldMapping("StandingOrderKEY", "StandingOrderKEY"));
BindingSource bs = new BindingSource();
bs.DataSource = new List<StandingOrder>();
_SchedulerControl.Storage.Appointments.DataSource = bs;
}
return _SchedulerControl;
}
}
and I am attempting to programmatically add an appointment with recurrence information, as in the examples given at http://help.devexpress.com/#WindowsForms/CustomDocument6201 . However, when the method execution reaches its final line (indicated) that adds the created appointment to the storage, it "hangs." No exception is ever thrown; I have left it running for upwards of 15 minutes with no change:
public void SetRecurrence(DateTime startDate, DateTime? endDate)
{
Appointment appointmentObj = ConvSchedulerControl.Storage.CreateAppointment(AppointmentType.Pattern);
if (endDate != null &&
endDate != DateTime.Parse("12/31/2999"))
{
appointmentObj.End = (DateTime)endDate;
}
else
{
appointmentObj.RecurrenceInfo.Range = RecurrenceRange.NoEndDate;
}
appointmentObj.Start = startDate;
appointmentObj.RecurrenceInfo.Type = RecurrenceType.Weekly;
appointmentObj.RecurrenceInfo.WeekDays = WeekDays.Monday;
appointmentObj.AllDay = true;
//Program execution reaches this line, but never proceeds past it.
ConvSchedulerControl.Storage.Appointments.Add(appointmentObj);
}
I would imagine that there's something wrong with the configuration that's preventing the storage from being able to successfully add the appointment, but I've been unable to turn up any other information on the subject. Does anyone know why this method isn't appropriate for adding an appointment to the storage, and how it can be corrected?
You've failed to provide a mapping for the 'End' field. This is a required mapping. Honestly, I only know this from having created a calendar in the designer. When you place a SchedulerControl onto a form/control, one of the things the designer gives you is a "Mappings Wizard". The 'Start' and 'End' fields are marked in the wizard as being required.
I need to pack string with a UTC datetime, using the smallest number of bytes/characters. I only need precision to the second. Using .NET 4.0, what would be the most space-efficient way to pack this down? Ticks doesn't seem all that small.
All ideas appreciated.
Thanks.
EDIT: Thanks to Joel Coehoorn, the pack/unpack move is the best. Thanks! Here is some proof:
DateTimeOffset nowStamp = DateTimeOffset.UtcNow;
Console.WriteLine( nowStamp.ToString() ); // 9/9/2011 2:17:17 PM +00:00
Console.WriteLine( nowStamp.ToString( "u" ) ); // 2011-09-09 14:17:17Z
Console.WriteLine( nowStamp.Ticks.ToString() ); // 634511746376767889
Console.WriteLine( PackDate( nowStamp ) ); // 7R9qTgAAAAA=
Console.WriteLine( UnpackDate( PackDate( nowStamp ) ) ); // 9/9/2011 2:17:17 PM +00:00
Perhaps a variant on unix time (seconds since 1/1/1970 rather than milliseconds) base64 encoded.
//Helpers
private static DateTime Jan1st1970 = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
public static long toUnixTime(this DateTime d)
{
return (long)((d.ToUniversalTime() - Jan1st1970).TotalMilliseconds);
}
public static string Base64Encode(long toEncode)
{
return Convert.ToBase64String(BitConverter.GetBytes(toEncode));
}
//Encode
public static string PackDate(DateTime toPack)
{
return Base64Encode(toPack.toUnixTime()/1000);
}
//Decode
public static DateTime UnpackDate(string toUnpack)
{
long time = BitConverter.ToInt64(Convert.FromBase64String(toUnpack),0);
return Jan1st1970.AddSeconds(time); //you may or may not want a "ToLocaltime()" call here.
}
Note that all this was done without the aid of an IDE - there's likely a bug or two above. But it should get you started.
This should result in a fixed-width string. Since we're only doing seconds rather than milliseconds, you may find you always have some extra padding in the result that you don't need. You might even be able to get away with an int, rather than a long, which will cut the string in half. Be careful stripping that padding out, though, as the closer you get to 1970 the smaller the number, but the farther you get the larger and the more likely you are to need it. You need to be certain that your date value will fit within the new, smaller range for doing any trimming. For example, the current date fits comfortably within an int, but even 28 years from now will not. UInt32 will get you a little further into the future, but prevent you from using dates before 1970.
If you rellay need to save some bytes, and dead sure about date-time bounds, this solution would work:
internal class Program
{
private static DateTime _lbound = new DateTime(2011, 1, 1).ToUniversalTime();
private static DateTime _ubound = new DateTime(2013, 1, 1).ToUniversalTime();
private static int Pack(DateTime utcTime)
{
var totalSeconds = (_ubound - _lbound).TotalSeconds;
return (int) (utcTime - _lbound).TotalSeconds;
}
private static DateTime Unpack(int packedTime)
{
return _lbound.AddSeconds(packedTime);
}
private static void Check(DateTime time)
{
var unpacked = Unpack(Pack(time));
var areEquals = Math.Abs((time - unpacked).TotalSeconds) < 1.0;
Console.WriteLine("Verify: {0} - {1}", time, areEquals);
}
static void Main(string[] args)
{
Check(_lbound);
Check(_ubound);
Check(DateTime.UtcNow);
}
}
It will fit time representation, with 1 second precision in defined time bounds (from 2011 till 2013) in 4 bytes (int). However, IMO it's really bad from maintenance perspective of view.
I have the following code:
var tempList = new BrandCollection();
if (HttpContext.Current.Cache["cachedDeviceList"] == null)
{
tempList = Provider.GetDeviceData(info);
HttpContext.Current.Cache.Insert(...);
}
else
{
tempList =
}
Cache.Insert() method is overloaded where I can set the dependencies, sliding and absolute expiration. I want to make the Cache expire at midnight. How do I do that? Thank in advance!
Absolute expiration is the way to do this - it's shorthand for 'this expires at an absolute point in time' as opposed to 'in twenty minutes from now'. So when you put an item into the cache, you need to calculate when midnight will be and then use that as the expiration point e.g.
var tempList = new BrandCollection();
if (HttpContext.Current.Cache["cachedDeviceList"] == null)
{
tempList = Provider.GetDeviceData(info);
// Find out when midnight will be by taking Today and adding a day to it
DateTime expirationTime = DateTime.Today.AddDays(1)
HttpContext.Current.Cache.Insert("cachedDeviceList", tempList, null, expirationTime, NoSlidingExpiration, CacheItemPriority.Normal, null);
}
else
{
...
}