I developed my blog engine around 2012 with asp.net mvc3 and after that, I've updated it several times. I have updated everything during the years. And not it's on version 3. I know that there are lots of open sources CMS out there and developing a custom one is meaningless! But I would like to have my own code to be run on my personal website, though it takes me a lot of time to update it. Unfortunately, I don't have any plan to make it as open source project, maybe in the future, I would change my Idea! So, we have to talk about the architecture of my website as you are not going to have the complete source. Actually, I have a class library named Domain and an MVC project:
I'm using Entity Framework as ORM and Castle Windsor as DI container and some other libraries for CDN management like Fluent FTP or FluentValidation. Let's consider an entity from domain to controller. For example, the let's consider the blog Entity. I have to summarize it as it’s got a lot of code and my main goal is to inform you of the overall implementation and architecture! The definition of blog Entity class is like this:
public class Blog { public int Id { get; set; } public string Title { get; set; } public string Body { get; set; } public string UrlSlug { get; set; } public DateTime CreationDate { get; set; } public bool IsPublished { get; set; } public bool IsDeleted { get; set; } public int? ViewCount { get; set; } public virtual ICollection<Tag> Tags { get; set; } }
It has a many to many relations with Tag:
public class Tag { public int Id { get; set; } public string Name { get; set; } public string UrlSlug { get; set; } public TagType TagType { get; set; } public virtual ICollection<Blog> Blogs { get; set; } } public enum TagType { Blog = 1, Interest = 2, Article = 4, PodCast = 8, Project = 16, Book = 32 }
TagType determines the type of my tag as it's not only just for Blog. For CRUD operations, I have a generic repository :
public interface IGenericRepository<T> { T GetById(int id); void Add(T entity); void Update(T entity); void Delete(int id); IList<T> GetAll(); bool Save(); }
And the implementation of it:
internal class GenericRepository<T> : IGenericRepository<T> where T : class { private const bool Disposed = false; private readonly SiteContext _siteContext; private readonly IDbSet<T> _dbset; public GenericRepository(SiteContext siteContext) { _siteContext = siteContext; _dbset = siteContext.Set<T>(); } public T GetById(int id) { return _dbset.Find(id); } public void Add(T entity) { _dbset.Add(entity); } public void Update(T entity) { _dbset.Attach(entity); _siteContext.Entry(entity).State = EntityState.Modified; // _siteContext.Entry(entity).CurrentValues.SetValues(entity); } public void Delete(int id) { var entity = _dbset.Find(id); _dbset.Remove(entity); } public IList<T> GetAll() { return _dbset.ToList(); } public bool Save() { try { _siteContext.SaveChanges(); return true; } catch (Exception exception) { return false; } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposing) { _siteContext.Dispose(); } }
Therefore for the IBlogRepository interface:
public interface IBlogRepository : IDisposable, IGenericRepository<Blog> { IEnumerable<Blog> GetAllPosts(); IEnumerable<Blog> GetAllPostsByTag(int tagId); IEnumerable<Blog> GetAllPostsByTag(string urlSlug); IEnumerable<Blog> GetPostByDate(int year); IEnumerable<Blog> GetPostByDate(int year, int month); int GetPostCountByDate(int year, int month); IEnumerable<Blog> GetAllPendingPosts(); }
And for the implementation of the above interface:
internal class BlogRepository : GenericRepository<Blog>, IBlogRepository { private readonly SiteContext _siteContext; public BlogRepository(SiteContext siteContext) : base(siteContext) { _siteContext = siteContext; } public IEnumerable<Blog> GetAllPosts() { return _siteContext.Blogs. Where(p => p.IsDeleted == false && p.IsPublished) .OrderByDescending(c => c.CreationDate) .Include(c => c.Tags) .ToList(); } /// <summary> /// returns all post by a tag /// it also contains other related tags to the post /// </summary> /// <param name="tagId"></param> /// <returns></returns> public IEnumerable<Blog> GetAllPostsByTag(int tagId) { return _siteContext.Blogs. Where(b => b.IsDeleted == false && b.IsPublished && b.Tags.Any(t => t.Id == tagId)).OrderByDescending(c => c.CreationDate); } public IEnumerable<Blog> GetAllPostsByTag(string urlSlug) { return _siteContext.Blogs. Where(b => b.IsDeleted == false && b.IsPublished && b.Tags.Any(t => t.UrlSlug == urlSlug)).OrderByDescending(c => c.CreationDate).ToList(); } public IEnumerable<Blog> GetPostByDate(int year) { return _siteContext.Blogs.Where(b => b.IsDeleted == false && b.IsPublished && b.CreationDate.Year == year) .OrderByDescending(c => c.CreationDate).ToList(); } /// <summary> /// return posts by data /// </summary> /// <param name="year"></param> /// <param name="month"></param> /// <returns></returns> public IEnumerable<Blog> GetPostByDate(int year, int month) { return _siteContext.Blogs.Where(b => b.IsDeleted == false && b.IsPublished && b.CreationDate.Year == year && b.CreationDate.Month == month).OrderByDescending(c => c.CreationDate).ToList(); } public int GetPostCountByDate(int year, int month) { return _siteContext.Blogs.Count(b => b.IsDeleted == false && b.IsPublished && b.CreationDate.Year == year && b.CreationDate.Month == month); } /// <summary> /// returns all pending posts of the system /// </summary> /// <returns></returns> public IEnumerable<Blog> GetAllPendingPosts() { return _siteContext.Blogs .Where(c => c.IsPublished == false && c.IsDeleted == false) .OrderBy(c => c.CreationDate).ToList(); } }
As you can see I've used something like Layer SuperType pattern in my repositories. The SiteContext is the simplest form of creating a context class in Entity Framework:
public class SiteContext : DbContext { public SiteContext():base("SiteContext") { } public DbSet<Blog> Blogs { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Configurations.Add(new BlogMapping()); } }
By the way, for mapping the Model via entity framework, I've written a class with the following structure:
internal class BlogMapping : EntityTypeConfiguration<Blog> { public BlogMapping() { ToTable("Blog"); HasKey(p => p.Id); Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); Property(p => p.Title).IsRequired().HasMaxLength(400); Property(p => p.UrlSlug).IsRequired().HasMaxLength(400); Property(p => p.CreationDate).IsOptional(); Property(p => p.Body).IsRequired(); Property(p => p.IsPublished).IsRequired(); Property(p => p.ViewCount).IsOptional(); Property(p => p.IsDeleted); HasMany(t => t.Tags).WithMany(p => p.Blogs).Map(m => { m.MapLeftKey("BlogId"); m.MapRightKey("TagId"); m.ToTable("Blog_Tag"); }); } }
And finally about the BlogController which is living in the MVC project:
public class BlogController : BaseController { private const int pageSize = 5; private readonly IBlogRepository _blogRepository; public BlogController(IBlogRepository postRepository) { _blogRepository = postRepository; } /// <summary> /// Index Action returns the latest ? blog Post to the first page of blog /// </summary> /// <returns></returns> public ActionResult List(int? page) { var blogPost = _blogRepository.GetAllPosts(); var blogViewModel = blogPost.ConvertToBlogViewModelList(); var pagedContent = new PaginatedList<BlogViewModel>(blogViewModel, page ?? 0, pageSize); return View("List", pagedContent); } public ActionResult Popular(int? page) { var posts = _blogRepository.GetPopularPosts(int.MaxValue); var blogViewModel = posts.ConvertToBlogViewModelList(); var pagedContent = new PaginatedList<BlogViewModel>(blogViewModel, page ?? 0, pageSize); return View("Popular", pagedContent); } /// <summary> /// return post by postSlug /// </summary> /// <param name="postId"></param> /// <param name="postSlug"></param> /// <returns></returns> [Route("Blog/Post/{postId}/{postSlug}")] public ActionResult Post(int postId, string postSlug) { var post = _blogRepository.GetPostWithTag(postId); if (post != null) { if (post.ViewCount == null) { post.ViewCount = 1; } else { post.ViewCount = post.ViewCount + 1; } _blogRepository.Update(post); _blogRepository.Save(); } var postViewModel = post.ConvertToBlogViewModel(); return View("Post", postViewModel); } /// <summary> /// Returns the future post to show in the side bar /// </summary> /// <returns></returns> [ChildActionOnly] [OutputCache(Duration = 6000)] public PartialViewResult PendingBlogPost() { var posts = _blogRepository.GetPendingPosts(5); var postViewModel = posts.ConvertToBlogViewModelList(); return PartialView("_PendingBlogPost", postViewModel); } [ChildActionOnly] [OutputCache(Duration = 6000)] public PartialViewResult PopularBlogPost() { var posts = _blogRepository.GetPopularPosts(5); var postViewModel = posts.ConvertToBlogViewModelList(); return PartialView("_PopularBlogPost", postViewModel); } /// <summary> /// Lists all posts of a tag /// </summary> /// <param name="urlSlug"></param> /// <param name="page"></param> /// <returns></returns> public ActionResult Tag(string urlSlug, int? page) { var posts = _blogRepository.GetAllPostsByTag(urlSlug); var blogViewModel = posts.ConvertToBlogViewModelList(); var pagedContent = new PaginatedList<BlogViewModel>(blogViewModel, page ?? 0, pageSize); ViewData["urlSlug"] = urlSlug; return View("Tag", pagedContent); } #region Archive [ChildActionOnly] [OutputCache(Duration = 6000)] public PartialViewResult ArchivedBlogPosts() { var archive = _blogRepository.GetArchive(); var blogArchiveViewModel = archive.ConvertToBlogArchiveViewModel(); return PartialView("_ArchivedBlogPosts", blogArchiveViewModel); } [Route("Blog/Archive/{year}/{page}")] public ActionResult Archive(int year, int? page) { var posts = _blogRepository.GetPostByDate(year); var blogViewModel = posts.ConvertToBlogViewModelList(); var pagedContent = new PaginatedList<BlogViewModel>(blogViewModel, page ?? 0, pageSize); ViewBag.YearOnly = "True"; return View("Archive", pagedContent); } [Route("Blog/Archive/{year}/{month}/{page}")] public ActionResult Archive(int year, int month, int? page) { var posts = _blogRepository.GetPostByDate(year, month); var blogViewModel = posts.ConvertToBlogViewModelList(); var pagedContent = new PaginatedList<BlogViewModel>(blogViewModel, page ?? 0, pageSize); ViewData["YearOnly"] = "False"; return View("Archive", pagedContent); } #endregion }
Finally, if you are interested in to see one of my razor pages:
@using BlogEngine.Mvc.ViewModels @model BlogEngine.Mvc.Helper.PaginatedList<BlogEngine.Mvc.ViewModels.BlogViewModel> @section Seo { @{ var seoViewModel = new SeoViewModel { Title = "Ehsan Ghanbari", PublishDate = Model.Select(d => d.CreationDate.ToLongDateString()).FirstOrDefault(), Description = Model.Select(d => d.SeoDescription).FirstOrDefault() }; Html.RenderPartial("_SeoPartial", seoViewModel); } } @Html.Partial("~/Views/Shared/_Header_Menu.cshtml") @Html.Partial("~/Views/Shared/_BreadCrumb.cshtml", new BreadCrumbViewModel { HeaderText = "Experience, DotNet, Solutions" }) <section class="blog_area section_padding_0_80"> <div class="container"> <div class="row justify-content-center"> <div class="col-12 col-lg-8"> @{ if (Model.Count != 0) { <div class="row no-gutters"> <div class="col-10 col-sm-11"> @foreach (var post in Model) { <div class="single-post"> <div class="post-content"> <h2 class="post-headline">@Html.ActionLink(post.Title, "Post", "Blog", new { postId = post.PostId, postSlug = post.UrlSlug }, null)</h2> <div class="post-meta d-flex"> <div class="post-author-date-area d-flex"> <div class="post-author"> <a href="http://www.twitter.com/ehsanghanbari">Ehsan Ghanbari</a> </div> <div class="post-date"> @post.CreationDate.ToLongDateString() </div> </div> </div> <p> @Html.Raw(post.Body) </p> </div> </div> <div class="postDetail"> <hr> <div class=" row justify-content-center"> @Html.ActionLink("View Detail", "Post", "Blog", new { postId = post.PostId, postSlug = post.UrlSlug }, null) </div> <hr> </div> <br /> <br /> } <nav class="navbar navbar-expand-md navbar-dark bg-white"> @if (Model.HasPreviousPage) { <div class="navbar-collapse collapse w-100 order-1 order-md-0 dual-collapse2"> <ul class="navbar-nav mr-auto"> <li class="nav-item active"> @Html.RouteLink("Previous Page", "PagedBlogRoute", new { page = (Model.PageIndex - 1) }) </li> </ul> </div> } @if (Model.HasNextPage) { <div class="navbar-collapse collapse w-100 order-3 dual-collapse2"> <ul class="navbar-nav ml-auto"> <li class="nav-item"> @Html.RouteLink("Next Page", "PagedBlogRoute", new { page = (Model.PageIndex + 1) }) </li> </ul> </div> } </nav> </div> </div> } } </div> <!-- ****** Blog Sidebar ****** --> @Html.Partial("~/Views/Shared/_Sidebar.cshtml", new BlogEngine.Mvc.ViewModels.SidebarViewModel() { IsBlog = true }) </div> </div> </section>
As I mentioned, I summarized even the mentioned codes and you cannot use them by copying and pasting because I didn't give a lot of helpers and classes of my blog to you! I just wanted to show you the overall structure and of the blog which is a part of my website. If I decide to make it as opensource, I will add the download link over here.
Category: Software