Click here to Skip to main content
13,772,358 members
Click here to Skip to main content
Add your own
alternative version

Stats

33.9K views
52 bookmarked
Posted 24 Mar 2017
Licenced MIT

Writing a Blog Engine in ASP.NET Core

, 27 Mar 2017
Rate this:
Please Sign up or sign in to vote.
Reflections on writing a simple blog engine in ASP.NET Core with Entity Framework Core

Introduction

This is my first article on CodeProject. My background is a mix of Network/Systems Engineering and Software Development. I first learned to program in QBasic when I was still in middle school and since then I've written some trivial and some not trivial programs in languages like JavaScript, C and C#. I personally find programming to be rewarding and enjoy solving technical problems. Unfortunately, sometimes I feel like I do more reading about programming than I do programming, so I figured I would reverse the trend a little bit and wound up writing a blogging engine so that I could write about programming.

So about two or three months ago I started writing my blogging engine. Along the way it grew into a fairly substantial project, and it got to the point where I came to the conclusion that it might be helpful to the community to share it. It's far from perfect, but it has some nice features like being able to categorize blog posts and a functional search capability, and it's gotten to the point where it's "good enough" to share.

This article is somewhat of a retrospective on my experience writing that blog engine using C# and ASP.NET Core. I'm going to go over some of the design considerations that played into which technology I chose, how it runs, where I feel I've made some mistakes, what I plan on changing, and what I've learned along the way.

I'm don't consider myself an expert in ASP.NET Core, although after this project I've certainly learned a little bit. My hope would be in reading this article you can see some of the things that I ran into choosing this technology, and see the impact from some of the design decisions that were made.

I called the blog engine Bagombo because I had registered the domain bagombo.org a long time ago and because I had read Bagombo Snuff Box. Working in software development for a while now it seems like naming things jointly is sometimes as hard as writing the code, and it was nice to be able to just pick a name without having to consult too many people : )

Bagombo as it is now isn't really a finished project, much like any software. But it got to the point where I have started to use it and I thought putting the source code out there and just releasing it may help someone starting out with ASP.NET Core and Entity Framework Core get started writing their own blog engine. I released it under the MIT license, so people are free to do whatever they want with it.

Below are some of the features of the blog engine:

  • Support for categorizing posts by Features or Category
  • Support for multiple authors
  • Local or Twitter authentication
  • Full text search of blog posts
  • Builtin editor for editing a post in markdown with preview

I'll talk about the design and implemention of these and what I learned along the way. I'm going to focus on the backend ASP.NET Core and EF Core parts of the code though, and won't address any of the front end design except to say I used basic HTML, Bootstrap and some JavaScript. It doesn't rely on any fancy JavaScript libraries like Angular or Backbone, it just uses a little jQuery and the highlight.js library for syntax highlighting.

First I'll go over how I ultimately wound up with the technology stack that I did.

The Technology Stack

I decided to write my blog engine in ASP.NET Core because when I heard about it I was stoked. .NET Core ran on the Mac and I didn't need to use Mono to get it to work. It came straight from Microsoft and with the neat new Visual Studio Code editor I thought it was awesome, plus I do some C# programming at work so I figured I would take the opportunity to write my blog engine using the latest and greatest and increase my skillset a bit.

Below are some of the initial reasons and thoughts I had for writing the blog in ASP.NET Core and not something like Node.js.

  • First, I like C#.
  • Second, ASP.NET Core MVC was released as open source, so it's possible to dig into the source code and go as far into it as I have the time or inclination to do. I thought this was a big draw.
  • Third, initially I planned on using Dapper as my ORM and MySQL so I could do all the development on my Mac and then when it came time to put it into production I could use a Linux server. This all changed though a little later.
  • Fourth, did I mention I thought I could use my Mac to do all the development on?

So my initial design thoughts based upon what I had heard about the software were as follows. I would do all of my development on the Mac and then move it to an Ubuntu or CentOS Linux server when I wanted to put it in production. I even thought about Docker but haven't quite gotten it that far. I could run MySQL on my Mac using Homebrew and when it came time to move it over it would be easy to get it running on Linux.

I liked MySQL from some projects I had used it for a while ago, and I like open source software especially when it's free and works well. I use Oracle 11g at work too and I have to say it works pretty well too, and since this was sort of an educational project I wanted to use the free Oracle XE with it, but at the time Oracle hadn't even announced whether or not they were going to support .NET Core. This has changed from what I understand now and they are going to support .NET Core from what I have read. But I liked MySQL and this was just a personal project so I figured I would use that. SQL Server isn't released on Linux yet from what I understand ... maybe there is a beta or something out, but I can run MySQL on my Mac!!

So I was set, ASP.NET Core, Dapper, MySQL, NGINX to reverse proxy it, Linux as the server, run it on AWS. Off I went ...

And then I ran into the first "gotcha". I needed authentication for my blog. Now, this is probably where I've either gone in the right or wrong direction from the start. Authentication is one of those things where I tend to lean towards taking the safe route, and being new with ASP.NET Core I didn't feel very comfortable rolling my own authentication because I really didn't know what I was doing with it. I mean, the worst that could happen is that someone hacks into my personal blog and destroys it and I have to restore it from backup, not the end of the world. But this was a learning exercise.

So I started digging through the Microsoft documentation for ASP.NET Core and while I think it's pretty good for the most part, I found the section on authentication to be lacking. But it talked about using Identity, which is as I think they put it, a "membership system" which allows you to use local authentication as well as fairly easily plug and authenticate using Twitter, Facebook, OAuth2 and things like that. I have a twitter account that I use to follow the news @itcheeze and figured setting up the Twitter authentication would be nice. I also plan to implement a comments feature for the blog, and if I get any readers making them sign up and remember another password is not something I wanted to do. I also had the book Pro ASP.NET Core MVC by Adam Freeman which was somewhere near 1000 pages long and had three chapters on Identity.

So, I thought, it seemed like a good idea to use Identity. And overall I think it was the right choice still. In the end I was able to plug it in and get it working without much trouble. I even got the Twitter authentication to work without fighting too much with it which was nice. I haven't plugged in any other external authentication but based upon my experience with Twitter I think that getting that to work won't be a stretch.

But it did throw the first wrinkle into how I had originally planned to do things. Identity, for anyone who wants to use it out of the box and just run with it, relies on Entity Framework Core, or EF Core. Ugh. I hadn't planned on using EF Core. My nice 1000 page Pro ASP.NET Core book had exactly zero chapters dedicated to EF Core. I didn't want to have to use two ORM libraries with my blog, and I had already done a little proof of concept fiddling around with Dapper and MySQL and was comfortable with that approach. Now I had to deal with EF Core. Although there was another option, Identity is open source! So I spent probably 3-4 hours digging through the source code for Identity. I came to the determination that it would be possible to implement some key interfaces for user stores and role stores and effectively factor EF Core out of the equation.

I gave it some serious thought. Ultimately I think that to do it I would have had to master Identity a little more than I wanted to. And looking at the classes that implemented those key interfaces for the stores which made use of EF Core, I decided it was a little hard to pick apart everything it was doing since I didn't know EF Core at all. What to do? This was really the point in my design where all of the tech I had picked out to started to change direction a bit. Little did I know it that my Mac based development plan was going to change. It hasn't been all bad though. I am fond of my 2014 Macbook Pro though. It's a kick-ass computer ... as I write this on Windows running on VMware Fusion ...

I'll go a little more into my experience and the design of my model/data layer for the blog under the design section. I want to keep going with how I ultimately ended up with a far different stack of technology than I originally thought I would. Granted, some of the things that influenced these changes have probably changed by now, but I was starting to make headway, and I wanted my blog software. Bagombo wasn't going to write itself.

So, to use Identity, I had to use EF Core (as far as I know this is the only way to do it without implementing the backing stores it requires, I searched on Google for a long time trying to find someone who has done it without finding anyone, and I'm considering it for a project someday).

Okay, so my project has a new dependency. At this point I do a little research on EF Core and start to warm up to the idea. I can't find a ton of documentation online, and I still don't claim to understand all that it does and how it works. Also, using what it calls a "Code First" approach to modeling your data, it magically creates the database and all of the schema that you need, based upon your code. Well it isn't quite magic, and it turns out that, being a new piece of software, not every database provider has a finished EF Core provider. I tried the official MySQL provider and also one by a company called Sapient I think, and spent a while fighting with Nuget packages and playing around with different releases to try to get EF Core to do its magic and create my database and tables. I got no love. I tried it with SQLite and it worked beautifully. Hmm, at least at that point I knew that it wasn't something with my code or my fingers typing stuff in. I'll stick with that, I read that some people had gotten it to work with Sapient somewhere on the internet, but I couldn't.

So my first hurdle. In all honesty, I probably could have used SQLite for my project and been just fine. But I actually don't know SQLite that well and I really didn't want to use it. I mean, someday my blog is going to have all of these readers and I'll make a ton of money with it and all of that stuff and then what would I do? Well I gave it some serious consideration because I am kind of lazy. But it just didn't sit right with what I wanted to do. Hmmmmm. So that meant ... SQL Server. And that meant ... Windows. Well not all was lost. I could still do all of my development on my Mac and just point my database connection to a Windows VM or take the time and run Docker or something like that and all would be right with the world. I'm at the point where I'm not really too picky about what it would run on in production as long as it works well.

But then I ran into wrinkle number two, which wasn't as big really, but when put together with wrinkle number one, well ...

Visual Studio Code (which I still like and use for Go programming and Powershell scripting) doesn't have Intellisense for Razor Pages!!!!!

So I tried Visual Studio 2015 and of course, it worked pretty well there. And I'm glad that I made that switch because I kind of count that as a requirement for development. If MySQL worked with EF Core a little better I probably would have stuck it out on the Mac. But I figured I'd just give it a try, and wound up continuing to do my development there. By the time I was in the middle of the project Visual Studio 2017 had just been released, so I even made the plunge of upgrading my package.json project to the new csproj based projects, and finished off my coding with that. I find it to be a nice development environment and actually enjoy using it for programming. I do like Visual Studio Code a lot though and find that I still have space for it.

So, original plan: ASP.NET Core, Dapper, MySQL, NGINX to reverse proxy it, Linux as the server, run it on AWS. Develop on my Mac. Actual plan: ASP.NET Core, EF Core, SQL Server 2016 Express, IIS to reverse proxy it, Windows Server 2016, run it on AWS. Develop on Windows.

Design

I found that at this point, most of the following design was primarily driven by my first choice, which was using ASP.NET Core MVC. If you're going to use it, I think it makes sense to follow it's conventions. Otherwise you'll be fighting the grain and it probably won't work that well for you. I'd never used it before, and I'm not going to go over every line of code I wrote or anything like that. In all honesty I had to read a whole book and search the internet a lot to get it to work, and it would take a whole book to describe how everything works.

I'll say this about it though. I knew C# pretty well, but everything else in ASP.NET Core was new to me. I'm pretty good at picking up new technologies quickly, and while this is just a basic blog engine, it took 2-3 months of part time work to get it going. I consider it at 0.2 Alpha right now jokingly.

I meant this article as a retrospective on my experience writing Bagombo, but I do want to include a little bit about the code and what I discovered along the way.

ASP.NET Core MVC has the MVC in it's title for a reason. It stands for Model View Controller, and its the coding design paradigm that it uses for its operations. It won't stop you from writing a lot of code that you probably shouldn't directly in your Razor pages, or formatting a bunch of raw HTML in your Controller and dumping it down to the browser. I hate to call myself an expert, but I think you'd be pretty silly to use MVC and then do that kind of thing. In effect I guess, you wouldn't be using MVC.

So the code for Bagombo is broken up in in a way to follow the conventions of MVC as much as possible. The core of the code that does anything is in the controllers. I have five of them in the source code currently, and one of them, BlogApiController I plan on removing because I basically at an early point stopped doing anything SPA like. All it does now is have a method to delete a blog post. Right now it's kind of silly how I have it rigged up, but it responds to a post from a page form to delete a blog post, then it redirects back to that page. It works, but I need to move that code into the Admin Controller, which would make more sense based upon the fact that it's from an Admin Manage Posts page where this call is made. I actually don't plan on talking too much about how to use the blog engine, because if you play around with it I think it's pretty easy to figure out. But as a side note, I made it so that only an Admin can delete a post because I didn't write a "Are you sure you want to delete the post?" confirmation, and I didn't want posts deleted on accident.

So the controllers in Bagombo basically handle getting data from the database, putting it into a ViewModel POCO, and returning it to the right view. Most of the logic for the application takes place in the controllers. There is a design consideration that I made early on, which was to not use a pattern like the Repository pattern, or something like CQRS. So I have a strong dependency currently on EF Core, and all of the queries are coded in action methods for the controllers. For a project like this I really didn't have a problem doing that, but I am thinking that before I consider it a 1.0 release I am going to adopt either something like the Repository or CQRS and factor this code out so that I have less of a dependency on EF Core. At the time I didn't think about it (I did kind of just whip this thing up) and I have a pretty strong dependency on EF Core anyway using Identity, unless I get really ambitious and implement the needed interfaces so that I can use Identity without EF Core.

My model is really pretty simple. I extended the IdentityUser class which represents a user in the Identity framework into an ApplicationUser class and added a one-to-one relationship to the Author class. I have classes for blogposts, categories and features. A feature in Bagombo is another way of organizing blogposts. I've seen some blogs where I find it really hard to find older blogs or sort just by category. For Bagombo I thought of Features which are analogous to either chapters or topics as another way for authors to sort their posts. There is a many-to-many relationship between blogposts and categories, and blogposts to features. If you check out the code for these classes in under the Models folder you'll see how its represented in C# code. There are also classes which are linking tables BlogpostCategory and BlogpostFeature. EF Core needs these to establish the many-to-many relationship, but from my understanding in a future version of the software it will support this without creating these classes to represent the linking tables.

Below is an image of the data model for bagombo:

From this to using the Code First is a short step with EF Core. I have another class, BlogDbContext, under the Data folder, which represents the database context. It has member variables of type DbSet<T> which represent the tables in your database. In Bagombo's case, BlogDbContext inherits from IdentityDbContext<ApplicationUser> because of it's use of Identity. This is where some other tables which get created for Identity in your database get hidden away, but you can find the code online if you're curious. As far as setting up the database for Identity this is actually all thats required.

Within BlogDbContext there is a constructor which calls its base constructor. Then there is another function OnModelCreating, which lets you use EF Core's Fluent API to configure how the tables are setup in the database and certain properties. I've used it to set certain things like the relationships between tables, and actions to take when an entry is deleted. I find the API to be pretty readable, and if you understand the basics about database design you'll probably see how it all comes together.

Below is the code for my BlogDbContext class. In the OnModelCreating method the relationships between the classes are described using the EF Core Fluent API. You can also see that it inherits from IdentityDbContext<ApplicationUser> which brings in the tables for Identity as well. I have two static methods, CreateAuthorRole and CreateAdminAccount. These are called from the Startup class's Configure method and seed the database with an admin account specified in appsettings.json, and create the Author role in the database if it isn't there already. This is also a handy way of creating another administrator account if you forget the password that you specify to start with. You can also see in the OnModelCreating how I've specified what do do when an author record is deleted. Here I've made the cascading behavior to be SetNull, so that any existing blog posts aren't deleted when an author is removed. You can also see how the many-to-many relationships are configured between blog posts and categories and features, using the linking tables.

// BlogDbContext.cs  -- under the Data Folder in the solution
namespace blog.Data
{
  public class BlogDbContext : IdentityDbContext<ApplicationUser>
  {
    public DbSet<BlogPost> BlogPosts { get; set; }
    public DbSet<Author> Authors { get; set; }
    public DbSet<Feature> Features { get; set; }
    public DbSet<BlogPostFeature> BlogPostFeature { get; set; }
    public DbSet<BlogPostCategory> BlogPostCategory { get; set; }
    public DbSet<Category> Categories { get; set; }

    public BlogDbContext(DbContextOptions<BlogDbContext> options) : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
      base.OnModelCreating(builder);
      // Customize the ASP.NET Identity model and override the defaults if needed.
      // For example, you can rename the ASP.NET Identity table names and more.
      // Add your customizations after calling base.OnModelCreating(builder);
      builder.Entity<Author>().ToTable("Author");
      builder.Entity<Author>().HasAlternateKey(e => new { e.FirstName, e.LastName });
      builder.Entity<Author>().HasOne(e => e.ApplicationUser)
                              .WithOne(au => au.Author)
                              .OnDelete(Microsoft.EntityFrameworkCore.Metadata.DeleteBehavior.SetNull);
      //.OnDelete(Microsoft.EntityFrameworkCore.Metadata.DeleteBehavior.);

      builder.Entity<ApplicationUser>().HasOne(e => e.Author)
                                       .WithOne(a => a.ApplicationUser)
                                       .HasForeignKey<Author>(a => a.ApplicationUserId);


      builder.Entity<Author>().HasIndex(a => a.ApplicationUserId)
                              .IsUnique(false);

      builder.Entity<Author>().HasMany(a => a.BlogPosts)
                              .WithOne(bp => bp.Author)
                              .IsRequired(false)
                              .OnDelete(Microsoft.EntityFrameworkCore.Metadata.DeleteBehavior.SetNull);


      builder.Entity<BlogPost>().ToTable("BlogPost");

      builder.Entity<BlogPost>().HasOne(bp => bp.Author)
                                .WithMany(a => a.BlogPosts)
                                .HasForeignKey("AuthorId")
                                .IsRequired(false);
                                
      builder.Entity<Feature>().ToTable("Feature");
      builder.Entity<Category>().ToTable("Category");

      builder.Entity<BlogPostFeature>().HasKey(bpf => new { bpf.FeatureId, bpf.BlogPostId });
      builder.Entity<BlogPostFeature>().HasOne(bpf => bpf.BlogPost)
                                       .WithMany(bp => bp.Features)
                                       .HasForeignKey(bpf => bpf.BlogPostId);
      builder.Entity<BlogPostFeature>().HasOne(bpf => bpf.Feature)
                                       .WithMany(f => f.BlogPosts)
                                       .HasForeignKey(bpf => bpf.FeatureId);

      builder.Entity<BlogPostCategory>().HasKey(bpc => new { bpc.BlogPostId, bpc.CategoryId });
      builder.Entity<BlogPostCategory>().HasOne(bpc => bpc.BlogPost)
                                        .WithMany(bp => bp.Categories)
                                        .HasForeignKey(bpc => bpc.BlogPostId);
      builder.Entity<BlogPostCategory>().HasOne(bpc => bpc.Category)
                                        .WithMany(c => c.BlogPosts)
                                        .HasForeignKey(bpc => bpc.CategoryId);
    }

    public static async Task CreateAuthorRole(IServiceProvider serviceProvider)
    {
      RoleManager<IdentityRole> roleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();

      if (await roleManager.FindByNameAsync("Authors") == null)
      {
        IdentityResult result = await roleManager.CreateAsync(new IdentityRole("Authors"));
        if (!result.Succeeded)
        {
          throw new Exception("Error creating authors role!");
        }
      }
    }
    public static async Task CreateAdminAccount(IServiceProvider serviceProvider, IConfiguration configuration)
    {
      UserManager<ApplicationUser> userManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
      RoleManager<IdentityRole> roleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();

      string userName = configuration["Data:AdminUser:Name"];
      string email = configuration["Data:AdminUser:Email"];
      string password = configuration["Data:Adminuser:Password"];
      string role = configuration["Data:AdminUser:Role"];

      if (await userManager.FindByNameAsync(userName) == null)
      {
        if (await roleManager.FindByNameAsync(role) == null)
        {
          await roleManager.CreateAsync(new IdentityRole(role));
        }
        ApplicationUser user = new ApplicationUser
        {
          UserName = userName,
          Email = email
        };
        IdentityResult result = await userManager.CreateAsync(user, password);
        if (result.Succeeded)
        {
          await userManager.AddToRoleAsync(user, role);
        }
      }
    }
  }

}

This is, however, one of the spots that I ran into trouble with EF Core. That being with the many-to-many relationships. The problem isn't with defininig the relationship using the Fluent API. The documentation on the EF Core site for doing this is straight forward. The problem was later on getting the queries to work smoothly the way that code on the internet said that it would. It's quite possible that there is a bug in my code and that's the reason that it doesn't work as smoothly as it should. But in a case where I make use of the many-to-many relationship, such as finding the categories which are assigned to a particular blog post, I received null values back when including categories. I was able to work around this with making two queries and doing it manually. So I might be doing something wrong, but if you're using EF Core and having trouble with many-to-many queries, you may want to look at the code that I have in a method such as FeaturePosts within HomeController.cs as you can see below. There is a commented out line:

//var categories = p.Categories.Select(c => c.Category).ToList();

This is how most of the reference code suggested that I should be able to retrieve the data through the linking table, however I found that I had to make a few queries in actuality because this was returning null values. Granted, it's hard to find an exact duplicate of the query you're trying to make online on stack exchange or in someone else's blog all the time, so it's quite possible I just have to finesse it somewhere a little bit to make it work correctly. But this is what I found so far. I've put the code for FeaturePosts below so you can see what I'm talking about, and how I ended up crafting the queries to get access to the categories through the linking table.

// FeaturePosts -- from HomeController.cs
public async Task<IActionResult> FeaturePosts(long id)
{
    var feature = await _context.Features.FindAsync(id);

    var bps = await _context.BlogPostFeature
                            .Where(bpf => bpf.FeatureId == feature.Id && bpf.BlogPost.Public == true && bpf.BlogPost.PublishOn < DateTime.Now)
                            .Select(bpf => bpf.BlogPost)
                            .ToListAsync();

    var posts = await _context.BlogPosts
                .Include(bp => bp.Author)
                .Include(bp => bp.Categories)
                .Where(bp => bps.Contains(bp))
                .ToListAsync();

    List<ViewBlogPostViewModel> viewPosts = new List<ViewBlogPostViewModel>();

    foreach (var p in posts)
    {
        //var categories = p.Categories.Select(c => c.Category).ToList();

        var categoryIds = p.Categories.Select(c => c.CategoryId);

        var categories = await (from cat in _context.Categories
                                where categoryIds.Contains(cat.Id)
                                select cat).ToListAsync();

        var bpView = new ViewBlogPostViewModel()
        {
            Author = $"{p.Author.FirstName} {p.Author.LastName}",
            Title = p.Title,
            Description = p.Description,
            Categories = categories,
            ModifiedAt = p.ModifiedAt,
            Id = p.Id
        };
        viewPosts.Add(bpView);
    }

    ViewFeaturePostsViewModel vfpvm = new ViewFeaturePostsViewModel()
    {
        Feature = feature,
        BlogPosts = viewPosts
    };

    return View(vfpvm);
}

I'll go over this code a litte in FeaturePosts. This is a controller action that returns the blog posts that correspond to the feature passed in through the id parameter. There are actually two spots where I have to make an extra query to get data. The first query is where I find the blog posts through the BlogPostFeature table. I was able to get the posts through this query, but when I tried to include it's corresponding author and category in this query, that didn't work. So I had to make the next query directly from the BlogPosts table, and here include the Author and Categories for the post.

But unfortunately even this didn't include everything in its results for the Categories, which is through the many-to-many linking table. It did return the results for the Author just fine, and I was able to access that below without making any other queries to retrieve that.

So I ended up having to get the Category Id's for each corresponding post, which were availble thankfully through the query above (for some reason it didn't link up the object correctly). After that I then had to query the Category table directoy to include the categories which were present in the query above. You can see how I did this using the Contains method on the categoryIds list.

In the end I found this to be a workable solution, but it did lead to a number of extra queries, where if I'm correct I should have basically been able to get everything I need by including the Author and Categories from my first query to BlogPostFeatures. I don't get that much traffic and this was a small project so I didn't fret over it too much, but is one of the spots I'm working on and trying to improve.

Update - 3-27-2017

So it turns out I was doing the querying incorrectly, and relying a little too much on Intellisense to guide me here. It turns out that it is possible to get all the information that I needed without the extra queries. The correct way is to use Include, followed by ThenInclude two times to get the categories through the linking table. I found this out some help from Microsoft's support on the Entity Framework Core Issues page. The correct way to do this is:

    public async Task<iactionresult> FeaturePosts(long id)
    {
      var feature = await _context.Features.FindAsync(id);

      if (feature == null)
      {
        return NotFound();
      }

      var bpfs = await _context.BlogPostFeature
                              .Where(bpf => bpf.FeatureId == feature.Id && bpf.BlogPost.Public == true && bpf.BlogPost.PublishOn < DateTime.Now)
                              .Include(bpf => bpf.BlogPost)
                                .ThenInclude(bp => bp.Author)
                              .Include(bpf => bpf.BlogPost)
                                .ThenInclude(bp => bp.BlogPostCategory)
                                .ThenInclude(bpc => bpc.Category)
                              .ToListAsync();

      List<ViewBlogPostViewModel> viewPosts = new List<ViewBlogPostViewModel>();

      foreach (var bpf in bpfs)
      {
        var bpView = new ViewBlogPostViewModel()
        {
          Author = $"{bpf.BlogPost.Author.FirstName} {bpf.BlogPost.Author.LastName}",
          Title = bpf.BlogPost.Title,
          Description = bpf.BlogPost.Description,
          Categories = bpf.BlogPost.BlogPostCategory.Select(c => c.Category).ToList(),
          ModifiedAt = bpf.BlogPost.ModifiedAt,
          Id = bpf.BlogPost.Id
        };
        viewPosts.Add(bpView);
      }

      ViewFeaturePostsViewModel vfpvm = new ViewFeaturePostsViewModel()
      {
        Feature = feature,
        BlogPosts = viewPosts
      };

      return View(vfpvm);
    }
</iactionresult>

Ultimately I found that EF Core seems to perform well enough though, and I have done some rudimentary load testing on this page and others on my server which is running up on AWS. It was able to handle about 100 requests per second (It's not on some super powerful dedicated server) on the database access pages, and didn't seem to crash and burn, which is more traffic than I'll probably get anyway, so I think it's okay for the most part.

The View part of the MVC is basically all of my Razor pages. I have to say that this is my least favorite part of programming for the web, although frameworks like Bootstrap have made it easier, and using Highlight.js I was able to plug in some really cool syntax highlighting for code samples. So I think it looks okay for the most part. I was also able to put together an editor for authoring pages where you write your post in markdown. This uses a little bit of Ajax for a preview feature which sends the markdown to a controller which parses the markdown and returns HTML back to the page. This is probably the only spot in the web application where I used Ajax to communicate from the client to the server, but getting it to work was suprisingly easy. I used a library called CommonMark.Net which is avaiable as a Nuget package to implement this. It took all of one function call to make it work. The code is in the AuthorController and the client side code is located in the EditPost.cshtml and AddEditPostPage.js file which does the actual Ajax.

I did find however once I got used to coding in Razor a little bit that it was easy to pull it all together and display things. I'm still working on making it look a little bit better, but using Bootstrap made it fairly easy to give the blog a relatively nice look and feel. Using Bootstrap's grid layout system also makes it so that the page displays very nicely on mobile devices, which was a real plus I find. I ended up implementing one tag helper, which is a class that allows you to customize some of the HTML for an element that it matches up with. The one that I made looks for a DIV element with a certain attribute set on it, and then sets its content to a message of passed down through the ViewContext. You can see how this works and how to access the ViewContext from a tag helper in TagHelpers/DivEditSaveUpdateTagHelper.cs.

So I haven't gone over the code that much directly, but if you want to look through the code and get a feel for how it all comes together I would recommend starting with the file Startup.cs. This is the startup class that is specified in Program.cs to be used by the the web host builder and contains the code which configures services and MVC for use and tells it how to respond to HTTP requests. From there I would probably recommend running the blog through Visual Studio 2017, which is the version of Visual Studio the project is setup for now.

If you want to run it the easiest way I found to do it while testing on my local system was to create a user that had create database permissions on my workstation so that EF Core was able to create the database from scratch if I had to drop it because I messed something up royally. After setting up the connection string (you need to specify one using either appsettings.json, the user secrets tools, or an environment varialbe) You should be able to run:

dotnet ef migrations add initial
dotnet ef database update

And if you have the user setup correctly it should create the database for you based upon your connection string. This needs to be run from the project folder on the command line for it to work. Before running the application you should change the settings in appsettings.json for your own email address or a dummy address, otherwise you'll be using my email and password to log in. It reads this file on application startup and creates a new admin user in the database if it isn't found there already ... I should probably change that. If you want the full text search to work you need to create a full text index on the BlogPost table in the database. This is as simple as right clicking on the table and following a short wizard from with SQL Server Management Studio. I used SQL Server 2016 Development Edition during the development and when I deployed it I ended up using the Express edition for licensing.

Summary

Well I learned a lot about ASP.NET Core MVC and how it comes together. I found that it was relatively straight forward to use, and that even though it is a new product, there is a good deal of information for it online. I also found that for the most part Microsoft's documentation for ASP.NET Core and EF Core is pretty good, although I wish they would go into more depth on EF Core. That project is open source as well, and if you're used to using a version of EF besides EF Core, the learning curve probably won't be as steep. That being said, I had not used LINQ much prior to starting this project, and I found that working with it and EF Core was pretty nice. I did manage to actually run into a bug in EF Core and I was able to confirm it was a bug thanks to the fact that they have the issues on Github, this was a relief so that I didn't spend too much time pulling my hair out over why something wasn't working. I still wish that the many-to-many relatinships worked a little bit better, and I get the feeling that if they did I would have been able to cut back on the number of queries that I had to write in LINQ.

Using Identity for the authentication in the end I think made sense for my blog. I did ultimately find some other code online where you can make use of some simple cookie authentication, but I had already started using Identity for the project and doing it that way didn't have the ease of integrating Twitter authentication into the project. The code for using Identity is contained within the Account class which handles logging in and out, and then the Admin class which has action methods for editing users. The messiest function I wrote is definitely the one for editing a user, and I need to come back to that to clean it up and shorten it a bit if I can.

In retrospect I think I could have gotten away with using MySQL if I didn't go with Identity. The dependency on EF Core drove a lot of the design on my app and ultimately my decision to go with SQL Server over MySQL. This was basically all due to using Identity and not wanting to have to use two ORMs.

If you are going to start a web application and plan on using ASP.NET Core I think that overall it was a nice environment to develop with. If you don't plan on using Identity for authentication I think that it would be easy to use another ORM, or just plain database access to manage your data, or if you don't need a database then you don't have to worry about any of that. If you do plan on using Identity then you are going to have to get your hands a little dirty with EF Core though. I think the template web application that Microsoft provides comes setup to use Sqlite with it so that you don't really have to do much to get it working, but if you want another database you will have to get a little comfortable with the one you choose. I'm fairly sure that if I messed with MySQL and the nuget packages available a little bit more I may have been able to get it to work by scripting the migrations and applying the schema updates using the scripts. There is also a way to go from an existing database model and have EF Core create your classes for you, but I haven't played around with this at all so I can't comment on how that works.

The only part left over that I still need to master is handling the flow of EF Core going from the development database to the production database. EF Core lets you output a SQL Script of the migrations that it applies which you can then run manually against a target database,

I started off writing Bagombo becuase I didn't come across that many open source blogs for ASP.NET Core yet, and I decided that I would just write my own. Overall there are a number of things that I plan on improving with it already, but it was a fun project, and it's basically usable now. The next item on my list of things to add to it is pagination support, so that when a lot of blog entries are returned it will be easier to page through them instead of having to scroll. This didn't make it in so far because I don't have that many blog entries at all. I also plan on changing the links to the blog entries so that the link uses the title instead of the blog id, this may be better for SEO as far as Google is concerned.

I also think that if I was to do it again I would just set it up so that there is a single user for the system. I'm guessing that most blogs are just used by a single author, although for now, Bagombo let's you have as many authors as you want.

Thanks for taking the time to read this and if you're considering writing your own blog engine in ASP.NET Core either as a learning exercise or to actually use I highly recommend trying it. I had basically zero experience with it before this and found that things really came together pretty well. The trickiest part sometimes was getting the queries to work with EF Core, but even that wasn't too bad after a while and as time passes there is more and more available online to see how to do it. Most of my queries are in the Home, Author, and Admin controllers, so you can take a look at this code to see how I did it. If you see something that I missed or if theres a better way to do it I'd love to hear from you. If you do make your own blog engine good luck, it's a fun project and there are a lot of different ways you can do it.

License

This article, along with any associated source code and files, is licensed under The MIT License

Share

About the Author

Tyler Rhodes
Software Developer
United States United States
I've been programming and working with computers professionally for over 10 years now.

It all started as a hobby a long time ago when a friend introduced me to QBasic.

From there I've programmed in a lot of languages like C, C++, C#, Java, JavaScript and now a little bit of Go. I don't really have a favorite, but if I had to pick I like C# right now.

I also have a long background of doing systems and network engineering and know about things like email and phone systems, Active Directory and setting up VPNs.


You may also be interested in...

Pro

Comments and Discussions

 
Praisevery nice article. Pin
vikas.address12-Jun-18 2:38
membervikas.address12-Jun-18 2:38 
QuestionThank you Tyler Pin
Member 1349207527-May-18 2:51
memberMember 1349207527-May-18 2:51 
Questionoracle 11g support for .net core + dapper Pin
mahonealex10-Oct-17 21:56
membermahonealex10-Oct-17 21:56 
AnswerRe: oracle 11g support for .net core + dapper Pin
Tyler Rhodes18-Nov-17 21:33
professionalTyler Rhodes18-Nov-17 21:33 
QuestionHow do I setup the initial sql database Pin
Dominic Chan3-Jun-17 9:36
memberDominic Chan3-Jun-17 9:36 
AnswerRe: How do I setup the initial sql database Pin
Tyler Rhodes14-Jun-17 13:45
professionalTyler Rhodes14-Jun-17 13:45 
PraiseGreat job Pin
Ahmad Hamdy30-Apr-17 0:55
memberAhmad Hamdy30-Apr-17 0:55 
SuggestionWhy not not contiuing on BlogEngine.Net project? Pin
jym59604-Apr-17 10:49
memberjym59604-Apr-17 10:49 
GeneralMy vote of 5 Pin
MathildePichard26-Mar-17 2:39
memberMathildePichard26-Mar-17 2:39 
QuestionFeature Question... Pin
Dewey24-Mar-17 10:56
memberDewey24-Mar-17 10:56 
AnswerRe: Feature Question... Pin
Tyler Rhodes24-Mar-17 12:01
professionalTyler Rhodes24-Mar-17 12:01 
GeneralRe: Feature Question... Pin
Dewey28-Mar-17 1:19
memberDewey28-Mar-17 1:19 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web04 | 2.8.181119.1 | Last Updated 28 Mar 2017
Article Copyright 2017 by Tyler Rhodes
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid