arctek.dev logo

ASP.NET Core Blog tutorial: The Backend

blog post cover image
to blog

ASP.NET Core Blog tutorial: The Backend

Assuming Direct Control

OK so we learned all about the MVC pattern, we set up the database and we even got out blog to be able to parse Markdown. That last part was super easy and short. Now let's get into something that'll take us a bit longer. If you've ever used any famous blogging platform out there you know that each one of them has an admin panel that you can only reach after you login. Well, we're not quite ready to set up authentication and authorization just yet.

But what we will do is create our very own backend from which you'll be able to write your blog posts and publish them. For this we'll need to create a couple of new files namely:

  • a controller named AdminController.cs
  • a new folder in your Views directory named Admin
  • a new view inside of your Admin folder named Index.cshtml
  • a new layout file in your Views/Shared named _adminLayout.cshtml

Let's first go ahead and fill out AdminController.cs with our basic boilerplate

using System;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Mvc;
using Blog.Models.Repos;
namespace Blog.Controllers{
    [Route("/admin")]
    public class AdminController : Controller{
        public IActionResult Index(){
            return View();
        }
    }
}

By now you should understand what each line of this does.

Next lets move into our _adminLayout.cshtml, now you might be asking yourself why not simply use the default _layout.cshtml, well optimally we don't wanna put our CSS and Javascript files meant for our frontend into our backend and we do not want our backend styles and scripts slowing down our frontend. Each request costs valuable time and resources and why waste that on something you won't even use.

Anyhow setup your _adminLayout.cshtml with a very basic HTML structure

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Admin Panel</title>
</head>
<body>
    @RenderBody()
</body>
</html>

Then finally add

@{
    Layout = "~/Views/Shared/_adminLayout.cshtml";
}

into your Views/Admin/Index.cshtml

if you run your ASP.NET blog application now and navigate to http://localhost:5000 you should see an empty page titled "Admin Panel"

now let's fill it with posts. Navigate back to your AdminController.cs and change your Index() Method to:

using System;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Mvc;
using Blog.Models.Repos;
namespace Blog.Controllers{
    [Route("/admin")]
    public class AdminController : Controller{
        private BlogRepo BlogRepo{get;set;}

        // The Controller constructor will be auto called everytime we 
        // navigate to one of the routes/methods in the controller
        public AdminController(){
            BlogRepo = new BlogRepo();
        }

        public IActionResult Index(){
            return View(BlogRepo.Posts.FindAll());
        }
    }
}

Then head back to your Views/Admin/Index.cshtml and add:

@{
    Layout = "~/Views/Shared/_adminLayout.cshtml";
}
<a href="/admin/new/post"><button class="new-post">New</button></a>
<div class="table">
    @foreach (var post in Model){
        <div class="row">
            <div class="cell">
                <input type="checkbox">
            </div>
            <div class="cell">
                @post.Title
            </div>
            
            <div class="cell control">
                <button class="edit" post="@post.Title.Replace(' ','-')">Edit</button>
            </div>
            <div class="cell control">
                <button class="delete" post="@post.Title.Replace(' ','-')">Delete</button>
            </div>
        </div>
    }
</div>

<style>
    .table{
        display:flex;
        flex-direction: column;
        width:100%;
    }
    .table .row{
        display:grid;
        grid-template-columns: auto 1fr 1fr 1fr;
        padding:5px 0px;
    }
    .table .row .cell.control{
        text-align: right;
    }
    .table .row:nth-child(2n+2){
        background: #eee;
    }
    .table a{
        text-decoration: none;
    }
</style>

Okay so we got a few more things that we're used to happening here, let's break it down. First, we add a button that links to /admin/new/post. This will later be handled by a controller and display a view that will allow us to easily write our posts.

Then we're using a bunch of divs that we class as table row and cell respectively, you might be asking yourself why not simply use the default <table> element. Well multiple reasons, first of all, it's unresponsive, second of all it comes with a bunch of predefined styles and I don't feel like undoing them all, it's much easier to make a quick CSS table using flex and grid. Also hey look you've learned how to make a responsive table with just HTML and CSS without using <table>.

Okay moving on! Next, we're displaying our post title next to a checkbox, which will allow us to select multiple posts at once for bulk operations later, furthermore we have an edit button and a delete button. You might have noticed that they are not linking to anything. This is intentional as I wish to show you how to combine javascript with ASP.NET next and maybe teach you some basics of RESTful API's and how you can use the basic principles of REST in your applications today.

and on that note just below our <style></style> tags add <script></script> tags. For this tutorial series, we'll be using pure vanilla ES6 javascript.

Okay leave the script tags empty for now and move back to our AdminController.cs. Let's take this chance to learn the very bare basics of REST, basically you got different HTTP methods meant for different things, let's take a look at them shall we:

GET

is the default HTTP method we use whenever we wish to request something from the server, every time you navigate to any webpage on the web, you send out a get request. For example, when you clicked on the link to this webpage you send out a GET request to my server and my server...well served you all the files required for you to view this webpage.

POST

Whenever you wish to send something to the server, for the server to handle, you wanna send out a POST request. Usually, when you register to a webpage or write a comment or add any type of information to the server you send out a POST request somewhere.

DELETE

This one should be fairly obvious. Whenever we wish for the server to remove some information or a file we send a DELETE request. When you delete your comments from social media you usually send a DELETE request. However most systems won't perform a hard delete on the data you wish to delete, but will instead opt for a soft delete, which means a value somewhere in a database will be set from deleted=false to deleted=true some systems and apps will keep your data in a soft-deleted state for a certain grace period, in case you change your mind and wish to recover your account or content and only after the grace period has passed will they hard delete it. While others will keep your data on soft delete forever and reserve your content or account for you if you change or mind or use it for ...their purposes. But that's a topic for a whole nother blog post.

PUT

You usually send a PUT request when you wanna update something on the server. If you change your display name on social media or change your workplace or even your relationship status, you are most likely sending a PUT request, I say most likely because there's no clear rule which method do use here. Some systems use POST to update, some use PUT some use PATCH. Whichever one of these you use is up to you, I like PUT and PATCH because IMHO they communicate our intent better than POST. In my eyes, POST should only be reserved for when we're adding data to the server.

Okay now that we got that covered, let's set up our controller keeping what we've just learned in mind.

using System.Linq;
using System;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Mvc;
using Blog.Models.Repos;
using Blog.Models;
namespace Blog.Controllers{
    [Route("/admin")]
    public class AdminController : Controller{
        private BlogRepo BlogRepo{get;set;}

        // The Controller constructor will be auto called everytime we 
        // navigate to one of the routes/methods in the controller
        public AdminController(){
            BlogRepo = new BlogRepo();
        }

        public IActionResult Index(){
            return View(BlogRepo.Posts.FindAll());
        }

        // Since this method expects a GET request 
        // we simply return the view responsible for
        // creating the posts in question
        [HttpGet, Route("new/post")]
        public IActionResult NewPost(){
            return View();
        }

        // This method won't return anything but a status code
        // that is because we'll be sending the post as a json string
        // via javascript and then reacting to it without reloading the page
        [HttpPost, Route("new/post")]
        public IActionResult NewPost([FromBody]Post post){
            BlogRepo.Posts.Insert(post);

            // set up the indices 
            BlogRepo.Posts.EnsureIndex(p => p.Title,true); // ensures that the title is unique
            BlogRepo.Posts.EnsureIndex(p => p._id, true); // ensures that the row has a valid bson index

            // check if the post has actually been created
            if(BlogRepo.Posts.Find(p => p.Title == post.Title).FirstOrDefault() != null ){
                // if the post actually exists return status code 201 - Created
                return StatusCode(201);
            }
            // We'll just assume something was wrong with the request
            // in an actual serious application we'd obviously do a bit more
            // to return a proper response to the user
            // which would allow the user to correct his mistake
            return StatusCode(400);
        }

        // Http DELETE method 
        // Pretty self explanatory if you ask me
        // [FromBody] means that we'll expect a value to be passed in the body 
        // of the request with the key of postTitle
        [HttpDelete, Route("post/delete")]
        public IActionResult DeletePost([FromBody]string postTitle){
            BlogRepo.Posts.Delete(p => p.Title == postTitle);
            // We check the exact opposite of what we checked in post create
            // namely if the post does not exist anymore then we're all good
            if(BlogRepo.Posts.Find(p => p.Title == postTitle).FirstOrDefault() == null){
                return StatusCode(200);
            }
            return StatusCode(400);
        }
        // Http PUT method expected
        // Stands for update in this case 
        [HttpPut, Route("post/update")]
        public IActionResult UpdatePost([FromBody]Post newPost, [FromBody]string postTitle){
            // Updating is a bit more complex because we obviously have to get the original post first
            // and then update it.
            // in this case LiteDB does mose of the work for us
            var postId = BlogRepo.Posts.Find(p => p.Title == postTitle ).FirstOrDefault()._id;
            bool status = BlogRepo.Posts.Update(postId, newPost);
            if(status){
                return StatusCode(200);
            }
            return StatusCode(400);
        }
    }
}

As you can see we've added a lot of new shit to the controller. Most of it is pretty self-explanatory, some of it will be a lot clearer when we get to the javascript part, which you'll sadly have to find in the next tutorial since this one is already dragging on quite a bit.

Thank you for joining me, if you have any questions feel free to hit me up at @arctekdev

Tuesday 03 December 2019