Can this extension method be improved?
public static string GetAvatar(this System.Security.Principal.IIdentity user)
{
var service = SmObjectFactory.Container.GetInstance<IApplicationUserManager>();
return service.FindByIdAsync(int.Parse(user.GetUserId())).Result.Avatar;
}
Now in my view I use the extension method this way:
User.Identity.GetAvatar()
But every time the page refreshes, it hits database for getting user's avatar.
I'd like to know this can be improved by caching that or something like that.
2 Answers 2
public static string GetAvatar(this System.Security.Principal.IIdentity user) { var service = SmObjectFactory.Container.GetInstance<IApplicationUserManager>(); return service.FindByIdAsync(int.Parse(user.GetUserId())).Result.Avatar; }
Do you know that you can access this static method not only as a extension method ? Assume the containing class is called UserExtensions
one could execute this method using UserExtensions.GetAvatar(null)
and if the creation of the service would take some time to be created it would nevertheless throw an ArgumentNullException
. So adding a guard condition to the method like
if (user == null) { throw new ArgumentNullException("user"); }
would be a good idea.
Otherwise the method is looking good.
By using a private static ConcurrentDictionary<string, string>
you could add caching to this like so
private static ConcurrentDictionary<string, string> avatarDictionary = new ConcurrentDictionary<string, string>();
public static string GetAvatar(this System.Security.Principal.IIdentity user)
{
if (user == null) { throw new ArgumentNullException("user"); }
string userId = user.GetUserId();
string avatar;
if (avatarDictionary.TryGetValue(userId, out avatar))
{
return avatar;
}
var service = SmObjectFactory.Container.GetInstance<IApplicationUserManager>();
avatar = service.FindByIdAsync(int.Parse(user.GetUserId())).Result.Avatar;
avatarDictionary.AddOrUpdate(userId, avatar, (key, oldValue) => avatar);
return avatar;
}
Based on @mjolka's comment
Using a ConcurrentDictionary as a cache in this manner could be problematic, depending on the number of users and the size of the avatars. As they say, "A cache without an expiration policy is another name for a memory leak."
you should add some mechanism to let the entries of the dictionary expire.
Like @DavidArno has correctly stated in his comment
And now you have a global method with static state, which is not a good place to be.
this isn't the best way to do this. By having a static dictionary this just can't be tested correctly, because the dictionary keeps its content between tests which is a no go.
A better way would be to create an interface having the GetAvatar()
method which is implemented by a simple class. The interface should then be constructor injected and stored in a private field.
public interface IAvatarService
{
string GetAvatar(System.Security.Principal.IIdentity user);
}
public class AvatarService : IAvatarService
{
private ConcurrentDictionary<string, string> avatarDictionary = new ConcurrentDictionary<string, string>();
public string GetAvatar(System.Security.Principal.IIdentity user)
{
if (user == null) { throw new ArgumentNullException("user"); }
string userId = user.GetUserId();
string avatar;
if (avatarDictionary.TryGetValue(userId, out avatar))
{
return avatar;
}
avatar = user.GetAvatar() // call of the extension method
avatarDictionary.AddOrUpdate(userId, avatar, (key, oldValue) => avatar);
return avatar;
}
}
having a expire mechanism would still be needed and should be implemented.
-
3\$\begingroup\$ Using a ConcurrentDictionary as a cache in this manner could be problematic, depending on the number of users and the size of the avatars. As they say, "A cache without an expiration policy is another name for a memory leak." \$\endgroup\$mjolka– mjolka2015年09月21日 07:51:09 +00:00Commented Sep 21, 2015 at 7:51
-
\$\begingroup\$ Hmm. Good idea. \$\endgroup\$Sirwan Afifi– Sirwan Afifi2015年09月21日 07:54:34 +00:00Commented Sep 21, 2015 at 7:54
-
\$\begingroup\$ And now you have a global method with static state, which is not a good place to be. \$\endgroup\$David Arno– David Arno2015年09月21日 09:44:17 +00:00Commented Sep 21, 2015 at 9:44
Since you want to cache the avatars, an extension method isn't really a good solution as it then forces you into having global state for the cache.
Instead, create a new wrapper class than encapsulates the user object and the caching of the avatar. The following is example code using C# 6 (you'll need to adjust the syntax if using C# 5 or earlier):
class UserWithAvatar
{
private string _avatar;
public UserWithAvatar(IIdentity user)
{
if (user == null) { throw new ArgumentNullException(nameof(user)); }
User = user;
}
public IIdentity User { get; }
public string Avatar => _avatar ?? _avatar = GetAvatar();
private string GetAvatar()
{
var service = SmObjectFactory.Container.GetInstance<IApplicationUserManager>();
return service.FindByIdAsync(int.Parse(user.GetUserId())).Result.Avatar;
}
}
-
\$\begingroup\$ The problem with this is : "Where do you cache the
UserWithAvatar
? This solution pushed the problem away but doesn't solve it. \$\endgroup\$IEatBagels– IEatBagels2015年09月21日 13:22:08 +00:00Commented Sep 21, 2015 at 13:22 -
\$\begingroup\$ @TopinFrassi, without access to the OP's entire application, that isn't a question that can be answered. \$\endgroup\$David Arno– David Arno2015年09月21日 13:25:10 +00:00Commented Sep 21, 2015 at 13:25
user.GetUserId()
return astring
which you then need toint.Parse
? \$\endgroup\$