diff --git a/modules/blogging/src/Volo.Blogging.Web/Areas/Blog/Helpers/TagHelpers/GravatarTagHelper.cs b/modules/blogging/src/Volo.Blogging.Web/Areas/Blog/Helpers/TagHelpers/GravatarTagHelper.cs
new file mode 100644
index 0000000000..45ff7060b1
--- /dev/null
+++ b/modules/blogging/src/Volo.Blogging.Web/Areas/Blog/Helpers/TagHelpers/GravatarTagHelper.cs
@@ -0,0 +1,210 @@
+using System;
+using System.ComponentModel;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Razor.TagHelpers;
+
+namespace Volo.Blogging.Areas.Blog.Helpers.TagHelpers
+{
+ ///
+ ///
+ /// Returns a Globally Recognised Avatar https://en.gravatar.com
+ ///
+ [HtmlTargetElement("img", Attributes = "gravatar-email")]
+ public class GravatarTagHelper : TagHelper
+ {
+ private readonly IHttpContextAccessor _contextAccessor;
+
+ public GravatarTagHelper(IHttpContextAccessor contextAccessor)
+ {
+ _contextAccessor = contextAccessor;
+ }
+
+ ///
+ /// Email Address for the Gravatar
+ ///
+ [HtmlAttributeName("gravatar-email")]
+ public string Email { get; set; }
+
+ ///
+ /// Gravatar content rating (note that Gravatars are self-rated)
+ ///
+ [HtmlAttributeName("gravatar-rating")]
+ public GravatarRating Rating { get; set; } = GravatarRating.GeneralAudiences;
+
+ ///
+ /// Size in pixels (default: 80)
+ ///
+ [HtmlAttributeName("gravatar-size")]
+ public int Size { get; set; } = 80;
+
+ ///
+ /// URL to a custom default image (e.g: 'Url.Content("~/images/no-grvatar.png")' )
+ ///
+ [HtmlAttributeName("default-image-url")]
+ public string DefaultImageUrl { get; set; } = "";
+
+ ///
+ /// Prefer the default image over the users own Gravatar
+ ///
+ [HtmlAttributeName("force-default-image")]
+ public bool ForceDefaultImage { get; set; }
+
+ ///
+ /// Default image if user hasn't created a Gravatar
+ ///
+ [HtmlAttributeName("default-image")]
+ public GravatarDefaultImage DefaultImage { get; set; } = GravatarDefaultImage.Default;
+
+ ///
+ /// Always do secure (https) requests
+ ///
+ [HtmlAttributeName("force-secure-request")]
+ public bool ForceSecureRequest { get; set; } = true;
+
+
+ public override void Process(TagHelperContext context, TagHelperOutput output)
+ {
+ var emailAddress = Email == null ? string.Empty : Email.Trim().ToLower();
+
+ var url = string.Format("{0}://{1}.gravatar.com/avatar/{2}?s={3}{4}{5}{6}",
+ GetUrlScheme(),
+ GetUrlPrefix(),
+ GetMd5Hash(emailAddress),
+ Size,
+ GetDefaultImageParameter(),
+ GetForceDefaultImageParameter(),
+ GetRatingParameter()
+ );
+
+ output.Attributes.SetAttribute("src", url);
+ }
+
+ private string GetUrlScheme()
+ {
+ return ForceSecureRequest || _contextAccessor.HttpContext.Request.IsHttps
+ ? "https" : "http";
+ }
+
+ private string GetUrlPrefix()
+ {
+ return ForceSecureRequest || _contextAccessor.HttpContext.Request.IsHttps ? "secure" : "www";
+ }
+
+ private string GetDefaultImageParameter()
+ {
+ return "&d=" + (!string.IsNullOrEmpty(DefaultImageUrl)
+ ? System.Net.WebUtility.UrlEncode(DefaultImageUrl)
+ : GetEnumDescription(DefaultImage));
+ }
+
+ private string GetForceDefaultImageParameter()
+ {
+ return ForceDefaultImage ? "&f=y" : "";
+ }
+
+ private string GetRatingParameter()
+ {
+ return "&r=" + GetEnumDescription(Rating);
+ }
+
+ ///
+ /// Generates an MD5 hash of the given string
+ ///
+ /// Source: http://msdn.microsoft.com/en-us/library/system.security.cryptography.md5.aspx
+ private static string GetMd5Hash(string input)
+ {
+ // Convert the input string to a byte array and compute the hash.
+ var data = MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(input));
+
+ // Create a new Stringbuilder to collect the bytes
+ // and create a string.
+ var sBuilder = new StringBuilder();
+
+ // Loop through each byte of the hashed data
+ // and format each one as a hexadecimal string.
+ foreach (var t in data)
+ {
+ sBuilder.Append(t.ToString("x2"));
+ }
+
+ // Return the hexadecimal string.
+ return sBuilder.ToString();
+ }
+
+ ///
+ /// Returns the value of a Description for a given Enum value
+ ///
+ /// Source: http://blogs.msdn.com/b/abhinaba/archive/2005/10/21/483337.aspx
+ ///
+ ///
+ private static string GetEnumDescription(Enum en)
+ {
+ var type = en.GetType();
+ var memInfo = type.GetMember(en.ToString());
+
+ if (memInfo == null || memInfo.Length <= 0)
+ {
+ return en.ToString();
+ }
+
+ var attrs = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
+
+ if (attrs != null && attrs.Any())
+ {
+ return ((DescriptionAttribute)attrs.First()).Description;
+ }
+
+ return en.ToString();
+ }
+
+ public enum GravatarDefaultImage
+ {
+ /// Default Gravatar logo
+ [Description("")]
+ Default,
+ /// 404 - do not load any image if none is associated with the email hash, instead return an HTTP 404 (File Not Found) response
+ [Description("404")]
+ Http404,
+ /// Mystery-Man - a simple, cartoon-style silhouetted outline of a person (does not vary by email hash)
+ [Description("mm")]
+ MysteryMan,
+ /// Identicon - a geometric pattern based on an email hash
+ [Description("identicon")]
+ Identicon,
+ /// MonsterId - a generated 'monster' with different colors, faces, etc
+ [Description("monsterid")]
+ MonsterId,
+ /// Wavatar - generated faces with differing features and backgrounds
+ [Description("wavatar")]
+ Wavatar,
+ /// Retro - awesome generated, 8-bit arcade-style pixelated faces
+ [Description("retro")]
+ Retro
+ }
+
+ ///
+ /// Gravatar allows users to self-rate their images so that they can indicate if an image is appropriate for a certain audience. By default, only 'G' rated images are displayed unless you indicate that you would like to see higher ratings
+ ///
+ public enum GravatarRating
+ {
+ /// Suitable for display on all websites with any audience type
+ [Description("g")]
+ GeneralAudiences,
+
+ /// May contain rude gestures, provocatively dressed individuals, the lesser swear words, or mild violence
+ [Description("pg")]
+ ParentalGuidance,
+
+ /// May contain such things as harsh profanity, intense violence, nudity, or hard drug use
+ [Description("r")]
+ Restricted,
+
+ /// May contain hardcore sexual imagery or extremely disturbing violence
+ [Description("x")]
+ OnlyMature
+ }
+ }
+}
diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/Detail.cshtml b/modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/Detail.cshtml
index d0f8b433bd..05c5c611b1 100644
--- a/modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/Detail.cshtml
+++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/Detail.cshtml
@@ -4,6 +4,7 @@
@using Microsoft.AspNetCore.Http.Extensions
@using Volo.Blogging
@using Volo.Blogging.Pages.Blog.Posts
+@using Volo.Blogging.Areas.Blog.Helpers.TagHelpers
@inject IAuthorizationService Authorization
@model DetailModel
@{
@@ -36,9 +37,6 @@