Browse Source

Blogging: Cache invalidation

pull/8491/head
EngincanV 6 years ago
parent
commit
6b3ae5fa55
  1. 2
      modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Blogs/IBlogManagementAppService.cs
  2. 14
      modules/blogging/src/Volo.Blogging.Admin.Application/Volo/Blogging/Admin/Blogs/BlogManagementAppService.cs
  3. 7
      modules/blogging/src/Volo.Blogging.Admin.HttpApi/Volo/Blogging/Admin/BlogManagementController.cs
  4. 16
      modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/index.js
  5. 1
      modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/Volo/Blogging/BloggingPermissionDefinitionProvider.cs
  6. 1
      modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/Volo/Blogging/BloggingPermissions.cs
  7. 10
      modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/BloggingApplicationAutoMapperProfile.cs
  8. 125
      modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Posts/PostAppService.cs
  9. 5
      modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/en.json
  10. 5
      modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/tr.json
  11. 1
      modules/blogging/src/Volo.Blogging.Domain/Volo.Blogging.Domain.csproj
  12. 5
      modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/BloggingDomainModule.cs
  13. 25
      modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Posts/PostCacheInvalidator.cs
  14. 39
      modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Posts/PostCacheItem.cs

2
modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Blogs/IBlogManagementAppService.cs

@ -18,5 +18,7 @@ namespace Volo.Blogging.Admin.Blogs
Task<BlogDto> UpdateAsync(Guid id, UpdateBlogDto input); Task<BlogDto> UpdateAsync(Guid id, UpdateBlogDto input);
Task DeleteAsync(Guid id); Task DeleteAsync(Guid id);
Task ClearCacheAsync(Guid id);
} }
} }

14
modules/blogging/src/Volo.Blogging.Admin.Application/Volo/Blogging/Admin/Blogs/BlogManagementAppService.cs

@ -3,18 +3,22 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Dtos;
using Volo.Abp.Caching;
using Volo.Blogging.Blogs; using Volo.Blogging.Blogs;
using Volo.Blogging.Blogs.Dtos; using Volo.Blogging.Blogs.Dtos;
using Volo.Blogging.Posts;
namespace Volo.Blogging.Admin.Blogs namespace Volo.Blogging.Admin.Blogs
{ {
public class BlogManagementAppService : BloggingAdminAppServiceBase, IBlogManagementAppService public class BlogManagementAppService : BloggingAdminAppServiceBase, IBlogManagementAppService
{ {
private readonly IBlogRepository _blogRepository; private readonly IBlogRepository _blogRepository;
private readonly IDistributedCache<List<PostCacheItem>> _postsCache;
public BlogManagementAppService(IBlogRepository blogRepository)
public BlogManagementAppService(IBlogRepository blogRepository, IDistributedCache<List<PostCacheItem>> postsCache)
{ {
_blogRepository = blogRepository; _blogRepository = blogRepository;
_postsCache = postsCache;
} }
public async Task<ListResultDto<BlogDto>> GetListAsync() public async Task<ListResultDto<BlogDto>> GetListAsync()
@ -63,5 +67,11 @@ namespace Volo.Blogging.Admin.Blogs
{ {
await _blogRepository.DeleteAsync(id); await _blogRepository.DeleteAsync(id);
} }
[Authorize(BloggingPermissions.Blogs.ClearCache)]
public async Task ClearCacheAsync(Guid id)
{
await _postsCache.RemoveAsync(id.ToString());
}
} }
} }

7
modules/blogging/src/Volo.Blogging.Admin.HttpApi/Volo/Blogging/Admin/BlogManagementController.cs

@ -54,5 +54,12 @@ namespace Volo.Blogging.Admin
{ {
await _blogManagementAppService.DeleteAsync(id); await _blogManagementAppService.DeleteAsync(id);
} }
[HttpGet]
[Route("clear-cache/{id}")]
public async Task ClearCacheAsync(Guid id)
{
await _blogManagementAppService.ClearCacheAsync(id);
}
} }
} }

16
modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/index.js

@ -52,6 +52,22 @@
}); });
}, },
}, },
{
text: l("ClearCache"),
visible: abp.auth.isGranted(
'Blogging.Blog.ClearCache'
),
confirmMessage: function (data) {
return l("ClearCacheConfirmationMessage");
},
action: function (data) {
volo.blogging.admin.blogManagement
.clearCache(data.record.id)
.then(function () {
_dataTable.ajax.reload();
})
}
}
], ],
}, },
}, },

1
modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/Volo/Blogging/BloggingPermissionDefinitionProvider.cs

@ -15,6 +15,7 @@ namespace Volo.Blogging
blogs.AddChild(BloggingPermissions.Blogs.Update, L("Permission:Edit")); blogs.AddChild(BloggingPermissions.Blogs.Update, L("Permission:Edit"));
blogs.AddChild(BloggingPermissions.Blogs.Delete, L("Permission:Delete")); blogs.AddChild(BloggingPermissions.Blogs.Delete, L("Permission:Delete"));
blogs.AddChild(BloggingPermissions.Blogs.Create, L("Permission:Create")); blogs.AddChild(BloggingPermissions.Blogs.Create, L("Permission:Create"));
blogs.AddChild(BloggingPermissions.Blogs.ClearCache, L("Permission:ClearCache"));
var posts = bloggingGroup.AddPermission(BloggingPermissions.Posts.Default, L("Permission:Posts")); var posts = bloggingGroup.AddPermission(BloggingPermissions.Posts.Default, L("Permission:Posts"));
posts.AddChild(BloggingPermissions.Posts.Update, L("Permission:Edit")); posts.AddChild(BloggingPermissions.Posts.Update, L("Permission:Edit"));

1
modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/Volo/Blogging/BloggingPermissions.cs

@ -13,6 +13,7 @@ namespace Volo.Blogging
public const string Delete = Default + ".Delete"; public const string Delete = Default + ".Delete";
public const string Update = Default + ".Update"; public const string Update = Default + ".Update";
public const string Create = Default + ".Create"; public const string Create = Default + ".Create";
public const string ClearCache = Default + ".ClearCache";
} }
public static class Posts public static class Posts

10
modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/BloggingApplicationAutoMapperProfile.cs

@ -20,6 +20,16 @@ namespace Volo.Blogging
CreateMap<Post, PostWithDetailsDto>().Ignore(x=>x.Writer).Ignore(x=>x.CommentCount).Ignore(x=>x.Tags); CreateMap<Post, PostWithDetailsDto>().Ignore(x=>x.Writer).Ignore(x=>x.CommentCount).Ignore(x=>x.Tags);
CreateMap<Comment, CommentWithDetailsDto>().Ignore(x => x.Writer); CreateMap<Comment, CommentWithDetailsDto>().Ignore(x => x.Writer);
CreateMap<Tag, TagDto>(); CreateMap<Tag, TagDto>();
CreateMap<Post, PostCacheItem>().Ignore(x=>x.Writer).Ignore(x=>x.CommentCount).Ignore(x=>x.Tags);
CreateMap<PostCacheItem, PostWithDetailsDto>()
.Ignore(x=>x.LastModificationTime)
.Ignore(x=>x.LastModifierId)
.Ignore(x=>x.DeleterId)
.Ignore(x=>x.IsDeleted)
.Ignore(x=>x.DeletionTime)
.Ignore(x=>x.CommentCount)
.Ignore(x=>x.Tags);
} }
} }
} }

125
modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Posts/PostAppService.cs

@ -20,9 +20,15 @@ namespace Volo.Blogging.Posts
private readonly IPostRepository _postRepository; private readonly IPostRepository _postRepository;
private readonly ITagRepository _tagRepository; private readonly ITagRepository _tagRepository;
private readonly ICommentRepository _commentRepository; private readonly ICommentRepository _commentRepository;
private readonly IDistributedCache<ListResultDto<PostWithDetailsDto>> _postsCache; private readonly IDistributedCache<List<PostCacheItem>> _postsCache;
public PostAppService(IPostRepository postRepository, ITagRepository tagRepository, ICommentRepository commentRepository, IBlogUserLookupService userLookupService, IDistributedCache<ListResultDto<PostWithDetailsDto>> postsCache) public PostAppService(
IPostRepository postRepository,
ITagRepository tagRepository,
ICommentRepository commentRepository,
IBlogUserLookupService userLookupService,
IDistributedCache<List<PostCacheItem>> postsCache
)
{ {
UserLookupService = userLookupService; UserLookupService = userLookupService;
_postRepository = postRepository; _postRepository = postRepository;
@ -33,21 +39,47 @@ namespace Volo.Blogging.Posts
public async Task<ListResultDto<PostWithDetailsDto>> GetListByBlogIdAndTagName(Guid id, string tagName) public async Task<ListResultDto<PostWithDetailsDto>> GetListByBlogIdAndTagName(Guid id, string tagName)
{ {
var cacheKey = id.ToString() + "-" + tagName; var posts = await _postRepository.GetPostsByBlogId(id);
var tag = tagName.IsNullOrWhiteSpace() ? null : await _tagRepository.FindByNameAsync(id, tagName);
var userDictionary = new Dictionary<Guid, BlogUserDto>();
var postDtos = new List<PostWithDetailsDto>(ObjectMapper.Map<List<Post>, List<PostWithDetailsDto>>(posts));
return await _postsCache.GetOrAddAsync( foreach (var postDto in postDtos)
cacheKey, {
async () => await GetPostsByBlogIdAndTagName(id, tagName), postDto.Tags = await GetTagsOfPost(postDto.Id);
() => new DistributedCacheEntryOptions }
if (tag != null)
{
postDtos = await FilterPostsByTag(postDtos, tag);
}
foreach (var postDto in postDtos)
{
if (postDto.CreatorId.HasValue)
{ {
AbsoluteExpiration = DateTimeOffset.Now.AddHours(6) if (!userDictionary.ContainsKey(postDto.CreatorId.Value))
{
var creatorUser = await UserLookupService.FindByIdAsync(postDto.CreatorId.Value);
if (creatorUser != null)
{
userDictionary[creatorUser.Id] = ObjectMapper.Map<BlogUser, BlogUserDto>(creatorUser);
}
}
if (userDictionary.ContainsKey(postDto.CreatorId.Value))
{
postDto.Writer = userDictionary[(Guid)postDto.CreatorId];
}
} }
); }
return new ListResultDto<PostWithDetailsDto>(postDtos);
} }
public async Task<ListResultDto<PostWithDetailsDto>> GetTimeOrderedListAsync(Guid blogId) public async Task<ListResultDto<PostWithDetailsDto>> GetTimeOrderedListAsync(Guid blogId)
{ {
return await _postsCache.GetOrAddAsync( var postCacheItems = await _postsCache.GetOrAddAsync(
blogId.ToString(), blogId.ToString(),
async () => await GetTimeOrderedPostsAsync(blogId), async () => await GetTimeOrderedPostsAsync(blogId),
() => new DistributedCacheEntryOptions () => new DistributedCacheEntryOptions
@ -55,6 +87,22 @@ namespace Volo.Blogging.Posts
AbsoluteExpiration = DateTimeOffset.Now.AddHours(6) AbsoluteExpiration = DateTimeOffset.Now.AddHours(6)
} }
); );
var postsWithDetails = ObjectMapper.Map<List<PostCacheItem>, List<PostWithDetailsDto>>(postCacheItems);
foreach (var post in postsWithDetails)
{
if (post.CreatorId.HasValue)
{
var creatorUser = await UserLookupService.FindByIdAsync(post.CreatorId.Value);
if (creatorUser != null)
{
post.Writer = ObjectMapper.Map<BlogUser, BlogUserDto>(creatorUser);
}
}
}
return new ListResultDto<PostWithDetailsDto>(postsWithDetails);
} }
public async Task<PostWithDetailsDto> GetForReadingAsync(GetPostInput input) public async Task<PostWithDetailsDto> GetForReadingAsync(GetPostInput input)
@ -157,62 +205,13 @@ namespace Volo.Blogging.Posts
return ObjectMapper.Map<Post, PostWithDetailsDto>(post); return ObjectMapper.Map<Post, PostWithDetailsDto>(post);
} }
private async Task<ListResultDto<PostWithDetailsDto>> GetPostsByBlogIdAndTagName(Guid id, string tagName) private async Task<List<PostCacheItem>> GetTimeOrderedPostsAsync(Guid blogId)
{
var posts = await _postRepository.GetPostsByBlogId(id);
var tag = tagName.IsNullOrWhiteSpace() ? null : await _tagRepository.FindByNameAsync(id, tagName);
var userDictionary = new Dictionary<Guid, BlogUserDto>();
var postDtos = new List<PostWithDetailsDto>(ObjectMapper.Map<List<Post>, List<PostWithDetailsDto>>(posts));
foreach (var postDto in postDtos)
{
postDto.Tags = await GetTagsOfPost(postDto.Id);
}
if (tag != null)
{
postDtos = await FilterPostsByTag(postDtos, tag);
}
foreach (var postDto in postDtos)
{
if (postDto.CreatorId.HasValue)
{
if (!userDictionary.ContainsKey(postDto.CreatorId.Value))
{
var creatorUser = await UserLookupService.FindByIdAsync(postDto.CreatorId.Value);
if (creatorUser != null)
{
userDictionary[creatorUser.Id] = ObjectMapper.Map<BlogUser, BlogUserDto>(creatorUser);
}
}
if (userDictionary.ContainsKey(postDto.CreatorId.Value))
{
postDto.Writer = userDictionary[(Guid)postDto.CreatorId];
}
}
}
return new ListResultDto<PostWithDetailsDto>(postDtos);
}
private async Task<ListResultDto<PostWithDetailsDto>> GetTimeOrderedPostsAsync(Guid blogId)
{ {
var posts = await _postRepository.GetOrderedList(blogId); var posts = await _postRepository.GetOrderedList(blogId);
var postDtos = new List<PostWithDetailsDto>(ObjectMapper.Map<List<Post>, List<PostWithDetailsDto>>(posts)); var postCacheItems = ObjectMapper.Map<List<Post>, List<PostCacheItem>>(posts);
foreach (var postDto in postDtos) return postCacheItems;
{
var creatorUser = await UserLookupService.FindByIdAsync(postDto.CreatorId.Value);
if (creatorUser != null)
{
postDto.Writer = ObjectMapper.Map<BlogUser, BlogUserDto>(creatorUser);
}
}
return new ListResultDto<PostWithDetailsDto>(postDtos);
} }
private async Task<string> RenameUrlIfItAlreadyExistAsync(Guid blogId, string url, Post existingPost = null) private async Task<string> RenameUrlIfItAlreadyExistAsync(Guid blogId, string url, Post existingPost = null)

5
modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/en.json

@ -12,6 +12,7 @@
"Permission:Posts": "Posts", "Permission:Posts": "Posts",
"Permission:Tags": "Tags", "Permission:Tags": "Tags",
"Permission:Comments": "Comments", "Permission:Comments": "Comments",
"Permission:ClearCache": "Clear cache",
"Title": "Title", "Title": "Title",
"Delete": "Delete", "Delete": "Delete",
"Reply": "Reply", "Reply": "Reply",
@ -53,6 +54,8 @@
"Blogs": "Blogs", "Blogs": "Blogs",
"Tags": "Tags", "Tags": "Tags",
"ShareOn": "Share on", "ShareOn": "Share on",
"TitleLengthWarning": "Keep your title size under 60 characters to be SEO friendly!" "TitleLengthWarning": "Keep your title size under 60 characters to be SEO friendly!",
"ClearCache": "Clear cache",
"ClearCacheConfirmationMessage": "Are you sure you want to clear the cache?"
} }
} }

5
modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/tr.json

@ -12,6 +12,7 @@
"Permission:Posts": "Yazılar", "Permission:Posts": "Yazılar",
"Permission:Tags": "Etiketler", "Permission:Tags": "Etiketler",
"Permission:Comments": "Yorumlar", "Permission:Comments": "Yorumlar",
"Permission:ClearCache": "Önbelleği temizle",
"Title": "Başlık", "Title": "Başlık",
"Delete": "Sil", "Delete": "Sil",
"Reply": "Yanıtla", "Reply": "Yanıtla",
@ -53,6 +54,8 @@
"Blogs": "Bloglar", "Blogs": "Bloglar",
"Tags": "Etiketler", "Tags": "Etiketler",
"ShareOn": "Paylaş", "ShareOn": "Paylaş",
"TitleLengthWarning": "Başlığınınz SEO dostu olabilmesi için 60 karakterden az olmasını sağlayın!" "TitleLengthWarning": "Başlığınınz SEO dostu olabilmesi için 60 karakterden az olmasını sağlayın!",
"ClearCache": "Önbelleği temizle",
"ClearCacheConfirmationMessage": "Önbelleği temizlemek istediğinizden emin misiniz?"
} }
} }

1
modules/blogging/src/Volo.Blogging.Domain/Volo.Blogging.Domain.csproj

@ -15,6 +15,7 @@
<ProjectReference Include="..\..\..\users\src\Volo.Abp.Users.Domain\Volo.Abp.Users.Domain.csproj" /> <ProjectReference Include="..\..\..\users\src\Volo.Abp.Users.Domain\Volo.Abp.Users.Domain.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AutoMapper\Volo.Abp.AutoMapper.csproj" /> <ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AutoMapper\Volo.Abp.AutoMapper.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Ddd.Domain\Volo.Abp.Ddd.Domain.csproj" /> <ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Ddd.Domain\Volo.Abp.Ddd.Domain.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Caching\Volo.Abp.Caching.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

5
modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/BloggingDomainModule.cs

@ -1,5 +1,6 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AutoMapper; using Volo.Abp.AutoMapper;
using Volo.Abp.Caching;
using Volo.Abp.Domain; using Volo.Abp.Domain;
using Volo.Abp.Domain.Entities.Events.Distributed; using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
@ -13,7 +14,9 @@ namespace Volo.Blogging
[DependsOn( [DependsOn(
typeof(BloggingDomainSharedModule), typeof(BloggingDomainSharedModule),
typeof(AbpDddDomainModule), typeof(AbpDddDomainModule),
typeof(AbpAutoMapperModule))] typeof(AbpAutoMapperModule),
typeof(AbpCachingModule)
)]
public class BloggingDomainModule : AbpModule public class BloggingDomainModule : AbpModule
{ {
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)

25
modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Posts/PostCacheInvalidator.cs

@ -0,0 +1,25 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.EventBus;
namespace Volo.Blogging.Posts
{
public class PostCacheInvalidator : ILocalEventHandler<EntityChangedEventData<Post>>, ITransientDependency
{
protected IDistributedCache<List<PostCacheItem>> Cache { get; }
public PostCacheInvalidator(IDistributedCache<List<PostCacheItem>> cache)
{
Cache = cache;
}
public virtual async Task HandleEventAsync(EntityChangedEventData<Post> eventData)
{
var cacheKey = eventData.Entity.BlogId.ToString();
await Cache.RemoveAsync(cacheKey);
}
}
}

39
modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Posts/PostCacheItem.cs

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities;
using Volo.Blogging.Tagging;
using Volo.Blogging.Users;
namespace Volo.Blogging.Posts
{
[Serializable]
public class PostCacheItem
{
public Guid Id { get; set; }
public Guid BlogId { get; set; }
public string Title { get; set; }
public string CoverImage { get; set; }
public string Url { get; set; }
public string Content { get; set; }
public string Description { get; set; }
public int ReadCount { get; set; }
public int CommentCount { get; set; }
[CanBeNull]
public BlogUser Writer { get; set; }
public List<Tag> Tags { get; set; }
public Guid? CreatorId { get; set; }
public DateTime CreationTime { get; set; }
}
}
Loading…
Cancel
Save