Skip to content

Commit 69d007b

Browse files
Need a caching mechanism that supports async
1 parent 2742a16 commit 69d007b

File tree

3 files changed

+94
-40
lines changed

3 files changed

+94
-40
lines changed

OurUmbraco/Forum/Controllers/LatestActivityController.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
using System.Linq;
2+
using System.Threading.Tasks;
23
using System.Web.Mvc;
3-
using OurUmbraco.Community.People;
44
using OurUmbraco.Forum.Services;
55
using Umbraco.Web.Mvc;
66

77
namespace OurUmbraco.Forum.Controllers
88
{
99
public class LatestActivityController : SurfaceController
1010
{
11-
public ActionResult LatestActivity(int numberOfTopics = 10)
11+
public async Task<ActionResult> LatestActivity(int numberOfTopics = 10)
1212
{
1313
var discourseService = new DiscourseService();
14-
var discourseTopics = discourseService.GetLatestTopics("questions", 5).Take(numberOfTopics).ToList();
14+
var discourseQuestions = await discourseService.GetLatestTopicsAsync("questions", 5);
15+
var discourseTopics = discourseQuestions.Take(numberOfTopics).ToList();
1516

1617
return PartialView("~/Views/Partials/Home/LatestForumActivity.cshtml", discourseTopics);
1718
}

OurUmbraco/Forum/Services/DiscourseService.cs

Lines changed: 87 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
using OurUmbraco.Forum.Extensions;
33
using OurUmbraco.Forum.Models;
44
using System;
5+
using System.Collections.Concurrent;
56
using System.Collections.Generic;
67
using System.Linq;
78
using System.Net.Http;
9+
using System.Runtime.Caching;
10+
using System.Threading;
811
using System.Threading.Tasks;
912

1013
namespace OurUmbraco.Forum.Services
@@ -47,51 +50,100 @@ internal async Task<List<DiscourseTopic>> GetLatestTopicsAsync(string categorySl
4750
{
4851
var cacheKey = "LatestDiscourseTopics" + categorySlug + categoryId;
4952

50-
return await Umbraco.Core.ApplicationContext.Current.ApplicationCache.RuntimeCache.GetCacheItemAsync(
51-
cacheKey,
52-
async () =>
53+
return await AsyncMemoryCache.GetOrAddAsync("myCacheKey", async () =>
54+
{
55+
var forumBaseUrl = System.Configuration.ConfigurationManager.AppSettings["DiscourseApiBaseUrl"];
56+
57+
using (var client = new HttpClient())
5358
{
54-
var forumBaseUrl = System.Configuration.ConfigurationManager.AppSettings["DiscourseApiBaseUrl"];
59+
client.BaseAddress = new Uri(forumBaseUrl);
60+
client.DefaultRequestHeaders.Add("Api-Key", $"{System.Configuration.ConfigurationManager.AppSettings["DiscourseApiKey"]}");
61+
client.DefaultRequestHeaders.Add("Api-Username", $"{System.Configuration.ConfigurationManager.AppSettings["DiscourseApiUsername"]}");
5562

56-
using (var client = new HttpClient())
63+
var endPoint = $"c/{categorySlug}/{categoryId}.json?order=created";
64+
var result = await client.GetAsync(endPoint);
65+
if (!result.IsSuccessStatusCode)
5766
{
58-
client.BaseAddress = new Uri(forumBaseUrl);
59-
client.DefaultRequestHeaders.Add("Api-Key", $"{System.Configuration.ConfigurationManager.AppSettings["DiscourseApiKey"]}");
60-
client.DefaultRequestHeaders.Add("Api-Username", $"{System.Configuration.ConfigurationManager.AppSettings["DiscourseApiUsername"]}");
67+
var resultContent = await result.Content.ReadAsStringAsync();
68+
var errorModel = JsonConvert.DeserializeObject<ErrorModel>(resultContent);
69+
string errors = string.Join(", ", errorModel.Errors);
6170

62-
var endPoint = $"c/{categorySlug}/{categoryId}.json?order=created";
63-
var result = await client.GetAsync(endPoint);
64-
if (!result.IsSuccessStatusCode)
65-
{
66-
var resultContent = await result.Content.ReadAsStringAsync();
67-
var errorModel = JsonConvert.DeserializeObject<ErrorModel>(resultContent);
68-
string errors = string.Join(", ", errorModel.Errors);
71+
// Logger.Debug(typeof(DiscourseController), $"Listing latest topics from {endPoint} didn't succeed: {errors}");
6972

70-
// Logger.Debug(typeof(DiscourseController), $"Listing latest topics from {endPoint} didn't succeed: {errors}");
71-
72-
return null;
73-
}
74-
else
73+
return null;
74+
}
75+
else
76+
{
77+
var resultContent = await result.Content.ReadAsStringAsync();
78+
var latestTopics = JsonConvert.DeserializeObject<TopicListModel>(resultContent);
79+
foreach (var topic in latestTopics.TopicList.Topics)
7580
{
76-
var resultContent = await result.Content.ReadAsStringAsync();
77-
var latestTopics = JsonConvert.DeserializeObject<TopicListModel>(resultContent);
78-
foreach (var topic in latestTopics.TopicList.Topics)
81+
var latestPostUser = topic.LastPosterUsername;
82+
var user = latestTopics.Users.FirstOrDefault(x => x.Username == latestPostUser);
83+
if (user != null)
7984
{
80-
var latestPostUser = topic.LastPosterUsername;
81-
var user = latestTopics.Users.FirstOrDefault(x => x.Username == latestPostUser);
82-
if (user != null)
83-
{
84-
topic.AuthorName = user.Name;
85-
topic.AuthorAvatar = $"{forumBaseUrl}{user.AvatarTemplate.Replace("{size}", "112")}";
86-
}
87-
topic.LastUpdatedFriendly = topic.LastPostedAt.ConvertToRelativeTime();
88-
topic.ForumCategory = "Umbraco questions";
85+
topic.AuthorName = user.Name;
86+
topic.AuthorAvatar = $"{forumBaseUrl}{user.AvatarTemplate.Replace("{size}", "112")}";
8987
}
90-
91-
return latestTopics.TopicList.Topics.OrderByDescending(x => x.LastPostedAt).ToList();
88+
topic.LastUpdatedFriendly = topic.LastPostedAt.ConvertToRelativeTime();
89+
topic.ForumCategory = "Umbraco questions";
9290
}
91+
92+
return latestTopics.TopicList.Topics.OrderByDescending(x => x.LastPostedAt).ToList();
9393
}
94-
}, TimeSpan.FromMinutes(5));
94+
}
95+
}, TimeSpan.FromMinutes(5));
9596
}
9697
}
9798
}
99+
100+
101+
public static class AsyncMemoryCache
102+
{
103+
private static readonly MemoryCache _cache = MemoryCache.Default;
104+
private static readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new ConcurrentDictionary<string, SemaphoreSlim>();
105+
106+
public static async Task<T> GetOrAddAsync<T>(string key, Func<Task<T>> valueFactory, TimeSpan absoluteExpiration)
107+
{
108+
if (_cache.Contains(key))
109+
{
110+
return (T)_cache.Get(key);
111+
}
112+
113+
var semaphore = _locks.GetOrAdd(key, _ => new SemaphoreSlim(1, 1));
114+
115+
try
116+
{
117+
await semaphore.WaitAsync();
118+
119+
// Double-check if the value was added while waiting for the lock
120+
if (_cache.Contains(key))
121+
{
122+
return (T)_cache.Get(key);
123+
}
124+
125+
T result;
126+
try
127+
{
128+
result = await valueFactory();
129+
}
130+
catch (Exception ex)
131+
{
132+
// Log the exception (optional)
133+
throw; // Re-throw or handle as needed
134+
}
135+
136+
if (result != null)
137+
{
138+
_cache.Add(key, result, DateTimeOffset.Now.Add(absoluteExpiration));
139+
}
140+
141+
return result;
142+
}
143+
finally
144+
{
145+
semaphore.Release();
146+
_locks.TryRemove(key, out _); // Clean up the lock
147+
}
148+
}
149+
}

OurUmbraco/Forum/Services/TopicService.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4+
using System.Threading.Tasks;
45
using System.Web;
56
using OurUmbraco.Forum.Extensions;
67
using OurUmbraco.Forum.Models;
@@ -419,7 +420,7 @@ public void Delete(Topic topic)
419420
/// <remarks>
420421
/// So that we don't have to look this up multiple times in a single request, this will use the given ICacheProvider to cache it
421422
/// </remarks>
422-
public ReadOnlyTopic CurrentTopic(HttpContextBase context, ICacheProvider cache, MemberData memberData, IPublishedContent content)
423+
public async Task<ReadOnlyTopic> CurrentTopicAsync(HttpContextBase context, ICacheProvider cache, MemberData memberData, IPublishedContent content)
423424
{
424425
var topic = (ReadOnlyTopic)cache.GetCacheItem(typeof(TopicService) + "-CurrentTopic", () =>
425426
{
@@ -505,7 +506,7 @@ public ReadOnlyTopic CurrentTopic(HttpContextBase context, ICacheProvider cache,
505506

506507
// Check if related topic exists on new forum
507508
var discourseService = new DiscourseService();
508-
var discourseTopic = discourseService.GetTopicByOldIdAsync(topic.Id);
509+
var discourseTopic = await discourseService.GetTopicByOldIdAsync(topic.Id);
509510
topic.DiscourseTopic = discourseTopic;
510511

511512
return topic;

0 commit comments

Comments
 (0)