22using OurUmbraco . Forum . Extensions ;
33using OurUmbraco . Forum . Models ;
44using System ;
5+ using System . Collections . Concurrent ;
56using System . Collections . Generic ;
67using System . Linq ;
78using System . Net . Http ;
9+ using System . Runtime . Caching ;
10+ using System . Threading ;
811using System . Threading . Tasks ;
912
1013namespace 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+ }
0 commit comments