We have a registration-type system that sends a confirmation e-mail once completed. The system had around 3000 registrations within a few minutes and we noticed a bug. If user A registers a few ms after user B has registered, user A will get user B’s details via e-mail. We did manage to fix the issue and I narrowed it down to this piece of code that gets the e-mail template from the cache and just does a string replace on a place holder.
private string ProcessEmailBody(MyRegistrationModel registration)
{
var content = CacheHelper.GetContent("REGISTRATIONEMAIL");
if (content != null)
{
content.Text = context.Text.Replace("@@FULL_NAME@@", registration.FullName);
return content.Text;
}
else return null;
}
The CacheHelper.GetContent() method is static and I fixed this “bug” by doing this:
private string ProcessEmailBody(MyRegistrationModel registration)
{
var content = CacheHelper.GetContent("REGISTRATIONEMAIL");
if (content != null)
{
string body = content.Text;
body = body.Replace("@@FULL_NAME@@", registration.FullName);
return body;
}
else return null;
}
And I can’t figure out for the life of me why this has fixed the issue. Can anybody shed any light on this?
EDIT: Here’s my GetContent() method (I know the signatures are different than above, I was being brief)
public static Content GetContent(string key, int partnerSiteId, int? version, IContentRepository contentRepository, out string cacheKey)
{
cacheKey = string.Format("{0}_{1}_{2}", key, partnerSiteId, version);
var content = CacheManager.Get(cacheKey, () => contentRepository.GetContent(key, partnerSiteId, version), WebConfig.GetCacheDuration(CacheProfile.Short));
return content;
}
private static DataCache _Cache = null; // DataCache is from AppFabric (Microsoft.ApplicationServer.Caching)
public static T Get<T>(string objectKey, Func<T> reloadItemExpresion, TimeSpan cacheDuration) where T : class
{
if (_Cache == null)
{
if (reloadItemExpresion != null)
{
return reloadItemExpresion.Invoke();
}
return null;
}
object cachedObject = null;
try
{
cachedObject = _Cache.Get(objectKey);
}
catch (Exception ex)
{
if (ex is FileNotFoundException)
{
_Cache.Remove(objectKey);
}
}
if (cachedObject != null)
{
return cachedObject as T;
}
if (reloadItemExpresion != null && cacheDuration > TimeSpan.Zero)
{
T item = reloadItemExpresion.Invoke();
if (item != null)
{
Insert(item, objectKey, cacheDuration);
}
return item;
}
return null;
}
contentRepository.GetContent just goes off to the database and fetches the actual content back.
The first time through you replace the
"@@FULL_NAME@@"tag incontext.Textwith the details of the first user. Once you do that, it never goes back to"@@FULL_NAME@@"again, so everybody gets that dude’s details until your cache is reset. You should avoid modifying objects that you get from cache: