Introducing PineBlog a new ASP.NET Core blogging engine

PineBlog Introducing PineBlog a new blogging engine, light-weight, open source and written in ASP.NET Core MVC Razor Pages, using Entity Framework Core. It is highly extendable, customizable and easy to integrate in an existing web application.

Why another blogging engine?

I've had a blog website for years (I do still need to move the old posts to this blog), and have been using various blogging engines. But I had some time lately and wanted to try some new things, so I thought lets build my own! When I started I knew there were a few things that I really wanted it to be/have:

  • Super easy installation, basically add a NuGet package and done
  • Modern architecture, I chose to use a Clean Architecture (youtube: Clean Architecture with ASP.NET Core)
  • Light-weight, just a blogging engine nothing more..
  • Write my posts in Markdown

So that is what I've been building :) So if you want to know more about it, please read on..
And I will write a more in depth blog post about Clean Architecture later.

Build Status NuGet Badge License: MIT

Features

  • Markdown post editor
  • File management
  • Light-weight using Razor Pages
  • SEO optimized
  • Open Graph protocol
  • Clean Architecture
  • Entity Framework Core, SQL database
  • Azure Blob Storage, for file storage
  • ..only a blogging engine, nothing else..

What is not included

Because PineBlog is very light-weight it is not a complete website, it needs to be integrated in an existing web application of you need to create a basic web application for it. There are a few things PineBlog depends on, but that it does not provide.

  • Authentication and authorization

Note: The admin pages require that authentication/authorization has been setup in your website, the admin area has a AuthorizeFilter with the default policy set to all pages in that area folder.

Where can I get it?

You can install the Opw.PineBlog metapackage from the console.

> dotnet add package Opw.PineBlog

The Opw.PineBlog metapackage includes the following packages.

  • Opw.PineBlog.EntityFrameworkCore package
    The PineBlog data provider that uses Entity Framework Core.
    NuGet Badge

  • Opw.PineBlog.RazorPages package
    The PineBlog UI using ASP.NET Core MVC Razor Pages.
    NuGet Badge

  • Opw.PineBlog.Core package
    The PineBlog core package. This package is a dependency for Opw.PineBlog.RazorPages and Opw.PineBlog.EntityFrameworkCore.
    NuGet Badge

Getting started

You add the PineBlog services and the RazorPages UI in the Startup.cs of your application.

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddPineBlog(Configuration);
    
    services.AddMvc().AddPineBlogRazorPages();
    // or services.AddMvcCore().AddPineBlogRazorPages();
    ...
}

Configuration

A few properties need to be configured before you can run your web application with PineBlog.

{
    "ConnectionStrings": {
        "DefaultConnection": "Server=inMemory; Database=pineblog-db;"
    },
    "PineBlogOptions": {
        "Title": "PineBlog",
        "Description": "A blogging engine based on ASP.NET Core MVC Razor Pages and Entity Framework Core",
        "ItemsPerPage": 5,
        "CreateAndSeedDatabases": true,
        "ConnectionStringName": "DefaultConnection",
        "AzureStorageConnectionString": "UseDevelopmentStorage=true",
        "AzureStorageBlobContainerName": "pineblog",
        "FileBaseUrl": "http://127.0.0.1:10000/devstoreaccount1"
    }
}

Blog layout page

For the Blog area you need to override the _Layout.cshtml for the pages, to do this create a new _Layout.cshtml page in the Areas/Blog/Shared folder. This will make the blog pages use that layout page instead of the one included in the Opw.PineBlog.RazorPages package. In the new page you can set the layout page of your website. Make sure to add the head and script sections.

@{
    Layout = "~/Pages/Shared/_Layout.cshtml";
}
@section head {
    @RenderSection("head", required: false)
}
@section scripts {
    @RenderSection("scripts", required: false)
}
@RenderBody()

Your layout page

PineBlog is dependent on Bootstrap 4.3 and Font Awesome 4.7, so make sure to include them in your layout page and add the necessary files to the wwwroot of your project (see the sample project for an example).

<html>
    <head>
        ...
        <environment include="Development">
            <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Merriweather:700">
            <link rel="stylesheet" href="~/css/bootstrap.css" />
            <link rel="stylesheet" href="~/css/font-awesome.min.css">
        </environment>
        <environment exclude="Development">
            <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Merriweather:700">
            <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
                asp-fallback-href="~/css/bootstrap.min.css"
                asp-fallback-test-class="sr-only"
                asp-fallback-test-property="position"
                asp-fallback-test-value="absolute"
                integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
                crossorigin="anonymous">
            <link rel="stylesheet" href="~/css/font-awesome.min.css" asp-append-version="true">
        </environment>
        ...
    </head>
    <body>
        ...
        <environment include="Development">
            <script src="~/js/jquery.js"></script>
            <script src="~/js/popper.min.js"></script>
            <script src="~/js/bootstrap.js"></script>
        </environment>
        <environment exclude="Development">
            <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"
                    asp-fallback-src="~/js/jquery.min.js"
                    asp-fallback-test="window.jQuery"
                    integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
                    crossorigin="anonymous">
            </script>
            <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"
                    integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1"
                    crossorigin="anonymous">
            </script>
            <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
                    asp-fallback-src="~/js/bootstrap.min.js"
                    asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
                    integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"
                    crossorigin="anonymous">
            </script>
        </environment>
    </body>
</html>

Overriding the UI

You can override any other Razor view you like by following the same steps as described above for the layout page. For an example have a look at the sample project where we override the footer (_Footer.cshtml).

Admin layout page

For the Admin area layout page do the same as you did for the Blog area.

...more

For more information, please check PineBlog on GitHub.

How to refresh the list of remote branches in Visual Studio Team Explorer

By default the Visual Studio Team Explorer does not refresh the remote branches when you fetch updates from the server. The fix for this is pretty easy, you need to set "Prune remote branches during fetch" to true in the "Git Settings". You can do this in the global settings or per repository.

  1. Go to Team Explorer settings
  2. Select either "Global Settings" or "Repository Settings"
  3. Set "Prune remote branches during fetch" to true
    Prune remote branches during fetch

And your done, now the remote branches will be updated every time you fetch or pull.
The only thing I don't understand is why "Prune remote branches during fetch" is not the default behavior.

Returning exceptions as ASP.NET Core Problem Details

Problem Details are a machine-readable format for specifying errors in HTTP API responses based on https://tools.ietf.org/html/rfc7807. By providing more specific machine-readable error responses, the API clients can react to errors more effectively and it also makes the APIs much more reliable from the REST API testing perspective and the clients as well.

Some background

For years I have been building WebAPIs for different projects and every time I have written similar code, or copied and updated code form a previous project, to return exceptions over HTTP in a uniform way.
In the end of 2016 I updated my code to return RFC 7807 Problem Details for HTTP APIs compatible responses. And then with the release of ASP.NET Core 2.1 when Problem Details where introduced into the framework I updated the code to use the default Microsoft.AspNetCore.Mvc.ProblemDetails.
When I recently started a new project and I was doing that same implementation again for the umpteenth time, I decided to make a package out of it. And I thought it would be fun to create an OSS project, my first one ;)

The project consists of two parts HttpExceptions and extensions for returning exceptions as ASP.NET Core Problem Details. Both can be found on GitHub at: github.com/ofpinewood/http-exceptions.

Opw.HttpExceptions.AspNetCore

This packages has extensions for returning exceptions as ASP.NET Core Problem Details.

NuGet Badge

Opw.HttpExceptions

This package contains HTTP-specific exception classes that enable ASP.NET to generate exception information. These classes can be used by themselves or as base classes for your own HttpExceptions.

NuGet Badge

Getting started

Add the HttpExceptions services and the middleware in the Startup.cs of your application. First add the required services to the services collection.

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddHttpExceptions();
    ...
}

Then you can add the HttpExceptions middleware using the application builder. UseHttpExceptions should be the first middleware component added to the pipeline. That way the UseHttpExceptions Middleware catches any exceptions that occur in later calls. When using HttpExceptions you don't need to use UseExceptionHandler or UseDeveloperExceptionPage.

public void Configure(IApplicationBuilder app)
{
    app.UseHttpExceptions(); // this is the first middleware component added to the pipeline
    ...
}

Configuring the HttpExceptions middleware

You can extend or override the default behavior through the configuration options, HttpExceptionsOptions.

Include exception details

Whether or not to include the full exception details in the response. The default behavior is only to include exception details in a development environment.

services.AddHttpExceptions(options =>
{
    // This is the same as the default behavior; only include exception details in a development environment.
    options.IncludeExceptionDetails = context => context.RequestServices.GetRequiredService<IHostingEnvironment>().IsDevelopment();
});

Is Exception Response

Is the response an exception and should it be handled by the HttpExceptions middleware.

services.AddHttpExceptions(options =>
{
    // This is a simplified version of the default behavior; only include exception details for 4xx and 5xx responses.
    options.IsExceptionResponse = context => (context.Response.StatusCode < 400 && context.Response.StatusCode >= 600);
});

Custom ExceptionMappers

Set the ExceptionMapper collection that will be used during mapping. You can override and/or add ExceptionMappers for specific exception types. The ExceptionMappers are called in order so make sure you add them in the right order.

By default there is one ExceptionMapper configured, that ExceptionMapper catches all exceptions.

services.AddHttpExceptions(options =>
{
    // Override and or add ExceptionMapper for specific exception types, the default ExceptionMapper catches all exceptions.
    options.ExceptionMapper<BadRequestException, BadRequestExceptionMapper>();
    options.ExceptionMapper<ArgumentException, ExceptionMapper<ArgumentException>>();
    // The last ExceptionMapper should be a catch all, for type Exception.
    options.ExceptionMapper<Exception, MyCustomExceptionMapper>();
});

Where can I get it?

The code can be found on GitHub at: github.com/ofpinewood/http-exceptions. And there is also a sample project you can have a look at.

You can install the Opw.HttpExceptions and Opw.HttpExceptions.AspNetCore NuGet packages from the package manager console:

PM> Install-Package Opw.HttpExceptions
PM> Install-Package Opw.HttpExceptions.AspNetCore