Making an ASP.NET Core front-end for WordPress – 1

I always try to find an excuse to code, I just love doing it. It rarely ends in a real life usable product, but I’m having a blast doing it.

For my latest project, I thought I would bring you around, just to get back into the blogging game, but also to keep my self on a schedule to, at least try to finalize this project.

Description

It’s no secret, my blog runs on WordPress, it has done so since 2007. I have tried, many times to move to Umbraco, but I never really got there, so here we are.

This project, is a way for me to start that transition. I want to move all public content away from the CMS, into a standalone application based on ASP.NET Core. This way, I will be able to isolate my WordPress backend and have a greater control over my frontend.

I will also like be able to try switching to Umbraco Headless or a full, local, Umbraco install without changing the frontend or needing to rebuild the front-end completely.

I usually use Visual Studio for my programming, but I will try to make this post as “cross platform” as possible.

My project names are all prefixed with NDesoft, feel free to call yours what you want.

I will also use my own WordPress API endpoint, feel free to use that as well. Everything is cached by Cloudflare, so go nuts.

Resources / Documentation

I am using the following resources to help me along in this endeavor:

ASP.NET Core: https://docs.microsoft.com/en-us/aspnet/core/index?view=aspnetcore-2.1

.NET Core CLI: https://docs.microsoft.com/en-us/dotnet/core/tools/?tabs=netcore2x

WordPress Rest API: https://developer.wordpress.org/rest-api/

Requirements

It must be based on ASP.NET Core.

Data access must be isolated from the web app, making it possible to add an App, Rest API or whatever later on without changing the code base.

CMS Api access must be isolated in a way that more can be added later on without rebuilding the applications completely from scratch.

This might make the project a bit over-engineered, but hey, we’re just having some fun here!

Getting started

First you must find a folder suitable for development. There, you open up a console, I prefer PowerShell, and run this command:

dotnet new sln

This creates a new Visual Studio solution file, nothing to it other than that.

Next we create a folder for our application, I call mine for “NDesoft.App”

In this folder we will create a new ASP.NET Core project, using the MVC template:

dotnet new mvc; dotnet sln ../NDesoft.sln add ./NDesoft.Web.csproj

This will create a new ASP.NET Core MVC project, and add it to our solution file.

Next, we need two more projects: One for wrapping the WordPress API, and one used as an interface between the web-app and the WordPress wrapper:

dotnet new classlib -o NDesoft.Wrapper;
dotnet new classlib -o NDesoft.Wrapper.WordPress;
dotnet sln add NDesoft.Wrapper;
dotnet sln add NDesoft.Wrapper.WordPress;

This will create two new folders, each with their own class library, (dll), project and add them to our solution file.

In each folder, the template has created a file called Class1.cs, please delete that. We don’t need that.

We’ll need to add some references between our newly created projects, to make them play nice together.

NDesoft.Web must have a reference to NDesoft.Wrapper and NDesoft.Wrapper.WordPress

NDesoft.Wrapper.WordPress must have a reference to NDesoft.Wrapper

To do that, we need to run the following commands:

dotnet add NDesoft.Web reference NDesoft.Wrapper NDesoft.Wrapper.WordPress;
dotnet add NDesoft.Wrapper.WordPress reference NDesoft.Wrapper;

Packages

We also need to install a few packages:

Newtonsoft.Json, AutoMapper and AutoMapper.Extensions.Microsoft.DependencyInjection

Automapper is used later on, but we might as well install it now.

dotnet add NDesoft.Web package automapper;
dotnet add NDesoft.Web package AutoMapper.Extensions.Microsoft.DependencyInjection;
dotnet add NDesoft.Web package Newtonsoft.Json;
dotnet add NDesoft.Wrapper package Newtonsoft.Json;
dotnet add NDesoft.Wrapper.WordPress package automapper;

And now, our solution and projects are up and running and we can start coding.

Interfaces

ASP.NET Core has native dependency injection, and since we are building a highly decoupled platform it makes sense to utilize that. So let’s start by adding a ton of interfaces. These will just be the bare minimum for this post, more will come.

In the NDesoft.Wrapper project, we’ll add a folder called “Interfaces” and under that another called “Helpers”.

In the Helpers folder, we’ll create to interfaces, (One file per interface):

IRequestManager

This interface manages requests to the Rest API and deserializes the responds, I might add an interface for the deserialization, but for now it’s handled by this manager.

public interface IRequestManager
{
    Task<TResult> Get<TResult>(string url, CancellationToken cancellationToken, object parameters = default);
}

IUrlParameterHelper

This interface helps us handle URL parameters in a shared way. No copy/pasting anything.

public interface IUrlParameterHelper
{
    string ConvertToUrlParameters(object parameters);
}

In the “Interfaces”-folder, we’ll create two new interfaces:

IPosts

This is an interface that handles everything related to blog posts. It has nothing to do with authors, tags, categories, media, comments, pages and so on, only posts.

Please note the “PostModel”-class, this will be implemented in a bit.

public interface IPosts
{
    Task<IEnumerable<PostModel>> GetPosts(int currentPage = 1, int? pageSize = default, CancellationToken cancellationToken = default);
}

IClient

The name might change, I do not like the name, but for now it’s IClient. It’s the main entry point handling almost everything.

public interface IClient
{
    IPosts Posts { get; }
}

Classes

Now we have the basic interfaces going, let’s implement them.

First of we need a class that represents a single blog post:

NDesoft.Wrapper

PostModel

Place this class in a folder, in the NDesoft.Wrapper project called “Models”.

public class PostModel
{
    public string Id { get; set; }
    public DateTime Date { get; set; }
    public DateTime Modified { get; set; }
    public string Slug { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public string Excerpt { get; set; }
    public string AuthorId { get; set; }
    public string FeaturedMediaId { get; set; }
    public bool IsSticky { get; set; }
    public IEnumerable<string> CategoryIds { get; set; }
    public IEnumerable<string> TagIds { get; set; }
}

This class is based off of the response from the following request for a single post:

GET https://ndesoft.dk/wp-json/wp/v2/posts/873

The properties contains, what I think is, the minimum required number of properties to display a blog post. You might want more or less.

You might want to add a reference to this class in the IPosts interface.

RequestManager

In NDesoft.Wrapper create a folder called Helpers. In here, create a class called RequestManager:

public class RequestManager : IRequestManager
{
    public static readonly HttpClient httpClient = new HttpClient();
    private readonly string baseUrl;
    private readonly IUrlParameterHelper urlParameterHelper;
 
    public RequestManager(string baseUrl, IUrlParameterHelper urlParameterHelper)
    {
        this.baseUrl = baseUrl;
        this.urlParameterHelper = urlParameterHelper;
    }
 
    public virtual async Task<TResult> Get<TResult>(string url, CancellationToken cancellationToken, object parameters = default)
    {
        var fullUrl = this.GetFullUrl(url, parameters);
 
        var response = await httpClient.GetAsync(fullUrl, cancellationToken).ConfigureAwait(false);
        response.EnsureSuccessStatusCode();
 
        var bodyContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
 
        var result = JsonConvert.DeserializeObject<TResult>(bodyContent);
        return result;
    }
 
    protected virtual string GetFullUrl(string url, object parameters = default)
    {
        var parameterString = this.urlParameterHelper.ConvertToUrlParameters(parameters);
        return $"{baseUrl}{url}{parameterString}";
}
}


Worth noting: The constructor takes a string as first parameter. It contains the base URL of the Rest API. The value is, later on, set using app settings. However, I would like to not add it as a parameter, since it adds to the complexity of setting up the dependency injector.

Also noteworthy, this class has a static readonly HttpClient. This way we always use the same HttpClient for all requests. I do know about RestSharp and the like, but I prefer HttpClient as I thinks it’s easier to use.

I have a habit to always pass a Cancellation token into every async method that supports it. To me it makes sense because if a user aborts a request, every subrequest I make on my site will be aborted as well releasing resources.

It might be beneficial to add some caching here to reduce response times and load on the source server.

UrlParameterHelper

Next to the RequestManager, add a class called “UrlParameterHelper”:

public class UrlParameterHelper : IUrlParameterHelper
{
    public string ConvertToUrlParameters(object parameters)
    {
        if (parameters == default)
        {
            return string.Empty;
        }
 
        var type = parameters.GetType();
        var properties = type.GetProperties();
        var urlBuilder = new StringBuilder();
 
        if (properties.Any())
        {
            urlBuilder.Append("?");
            string joinedParams = GetParameterString(parameters, properties);
            urlBuilder.Append(joinedParams);
        }
 
        return urlBuilder.ToString();
    }
 
    private static string GetParameterString(object parameters, PropertyInfo[] properties)
    {
        var urlParams = new List<string>(properties.Count());
        foreach (var property in properties)
        {
            var value = property.GetValue(parameters);
            if (value != null)
            {
                var stringValue = value.ToString();
                if (string.IsNullOrWhiteSpace(stringValue) == false)
                {
                    var encodedValue = HttpUtility.UrlEncode(stringValue);
                    var urlParam = $"{property.Name}={encodedValue}";
urlParams.Add(urlParam);
}
}
}
var parameterString = string.Join("&", urlParams.ToArray());
    return parameterString;
}

This class is used to help converting an object to url parameters, making it easier to create url’s with objects rather than string manipulation.

In the future, this class might be extended to handle number and datetime formatting better than it does now, as well as flattening nested properties.

Client

And now, the last class in the NDesoft.Wrapper project, before moving on to the WordPress specific classes. This class should be located in the root of the project.

public class Client : IClient
{
    public Client(
        IRequestManager requestManager, 
        IPosts posts)
    {
        this.RequestManager = requestManager;
        Posts = posts;
    }
 
    protected IRequestManager RequestManager { get; }
    public IPosts Posts { get; }
}

There’s nothing much to this class at the moment, but we will add more in a future post.

NDesoft.Wrapper.WordPress

Now we move on to some of the WordPress specific implementations.

Models

This is not a class but a folder, we need to create a lot of classes that represents the json returned from the WordPress Rest API, and instead of me posting all those classes I’ll tell you how to generate all those classes.

First get the Json, I use Postman:

GET https://ndesoft.dk/wp-json/wp/v2/posts/873

Copy the response body and:

In Visual Studio:

Edit -> Paste Special -> Paste JSON As Classes

Or use http://json2csharp.com/

Posts

In the root of the project, add a class called Posts:

public class Posts : IPosts
{
    private const string BaseUrl = "/wp/v2/posts";
    private readonly IRequestManager requestManager;
 
    public Posts(IRequestManager requestManager)
    {
        this.requestManager = requestManager;
    }
 
    public async Task<IEnumerable<PostModel>> GetPosts(int currentPage = 1, int? pageSize = null, CancellationToken cancellationToken = default)
    {
        var parameters = new
        {
            page = currentPage,
            per_page = pageSize
        };
 
        var response = await requestManager.Get<IEnumerable<Post>>(BaseUrl, cancellationToken, parameters).ConfigureAwait(false);
        return Mapper.Map<IEnumerable<PostModel>>(response);
    }
}

Some background here: The method “GetPosts” loads all posts from the API by calling this URL:

GET https://ndesoft.dk/wp-json/wp/v2/posts

However, loading all posts at the same time would be overkill and slow down response times and over time only become slower. So, we need to add some paging parameters to the URL.

How to do that, we’ll look at this page https://developer.wordpress.org/rest-api/using-the-rest-api/pagination/ it tells us that the API has two parameters we can use for our needs: “page” and “per_page”. Page, oddly enough is not zero-based so the first page is “1”.

PostProfile

Speaking of AutoMapper, we need some configuration to map the weird WordPress models the more leaner PostModel class. To do that, we need an AutoMapper profile.

Create a folder called “AutoMapperProfiles” and add a class called PostProfile:

public class PostProfile : Profile
{
    public PostProfile()
    {
        CreateMap<Post, PostModel>()
            .ForMember(dest => dest.Date, m => m.MapFrom(src => src.date_gmt))
            .ForMember(dest => dest.Modified, m => m.MapFrom(src => src.modified_gmt))
            .ForMember(dest => dest.Title, m => m.MapFrom(src => src.title.rendered))
            .ForMember(dest => dest.Content, m => m.MapFrom(src => src.content.rendered))
            .ForMember(dest => dest.Excerpt, m => m.MapFrom(src => src.excerpt))
            .ForMember(dest => dest.AuthorId, m => m.MapFrom(src => src.author))
            .ForMember(dest => dest.FeaturedMediaId, m => m.MapFrom(src => src.featured_media))
            .ForMember(dest => dest.IsSticky, m => m.MapFrom(src => src.sticky))
            .ForMember(dest => dest.CategoryIds, m => m.MapFrom(src => src.categories))
            .ForMember(dest => dest.TagIds, m => m.MapFrom(src => src.tags));
    }
}

Again, nothing special here. All we do is we flatten out some of the weird constructs of the WordPress json and maps everything to a more lean model.

Web

Now we have setup everything needed to access the WordPress API and have a solid base to build on. All we need to do now is wire everything up in the web project.

Configure AutoMapper

First things first, we need to configure AutoMapper. Open up the Startup-class and add the following method at the bottom of the class:

private void SetupAutoMapper(IServiceCollection services)
{
    services.AddAutoMapper(cfg =>
    {
        cfg.AddProfile(typeof(PostProfile));
    });
}

All we do here is adding AutoMapper to our services, and adds our PostProfile to the AutoMapper collection.

Setting up the Dependency Injector

Next up, adding our services to the Dependency Injector. Again in the Startup-class add the following method to the bottom of the class

private void SetupWrappers(IServiceCollection services)
{
    services.AddTransient<IUrlParameterHelper, UrlParameterHelper>();
    var baseUrl = Configuration["WrapperConfig:BaseUrl"];
    services.AddTransient<IRequestManager, RequestManager>((provider) => new RequestManager(baseUrl, provider.GetRequiredService<IUrlParameterHelper>()));
    services.AddTransient<IClient, Client>();
 
    // Service specifics:
    services.AddTransient<IPosts, Posts>();
}

All we do here is telling the DI, what services we have, and where to find their implementations. Note that everything uses the NDesoft.Wrapper classes, but only Posts uses the NDesoft.Wrapper.WordPress classes.

Before we’re done here, we need to add the following to the appsettings.json-file:

"WrapperConfig":{
      "BaseUrl":"https://ndesoft.dk/wp-json"
 }

By doing this, we tell the wrapper where to find the endpoints needed to get the data.

Finishing up Startup

In the ConfigureServices-method, add these two lines at the top of the method:

this.SetupAutoMapper(services);
this.SetupWrappers(services);

And now we are set, and can start building our controller.

Home

In the HomeController, remove everything except the Index-method and add the following:

private readonly IClient client;
 
public HomeController(IClient client)
{
    this.client = client;
}

This injects the client into the controller and we’re ready to use it.

Replace the Index-method with this:

public async Task<IActionResult> Index(CancellationToken cancellationToken)
{
    var posts =  await this.client.Posts.GetPosts(1, 5, cancellationToken).ConfigureAwait(false);
    return View(posts);
}

This code gets the first 5 posts from my blog and sends them to the view.

Final thing, we need to update the view. Open the file /Views/Home/Index.cshtml and replace everything with this:

@model IEnumerable<NDesoft.Wrapper.Models.PostModel>
@using System.Globalization;
@{
    ViewData["Title"] = "Home Page";
}
 
@foreach (var item in Model)
{
    <article>
        <a href="/@item.Date.ToString("yyyy/MM/dd", CultureInfo.InvariantCulture)/@item.Slug"><h2>@Html.Raw(item.Title)</h2></a>
        <div>@item.Date</div>
        <div>
            @Html.Raw(item.Content)
        </div>
    </article>
}

Trying it out

Let’s try to run the darn thing:

dotnet build;
dotnet run --project ./NDesoft.Web;

You should now be able to go to http://localhost:5000 or https://localhost:5001 and se something like this:

Source

The source code for this blog post can be found on Github:

https://github.com/nickfrederiksen/aspnetcore-wrapper-for-wordpress/tree/part-1

Next part

In the next part, I’ll try getting pages, author, tags and categories up and running.

Why I haven’t posted any articles since 2014

Back in 2014 I wrote a blog post about a new package I’ve created for Umbraco called “Create magic with APE“, that was my last post I did before this one. And here is why.

Please note, this is not a technical post, this is a very personal one so if you don’t like that, please move along.

In school I was, like so many other skinny pale nerds with a vocabulary of an adult, bullied. A lot. So much in fact that even the teachers either wouldn’t or couldn’t help. I was alone in school, I was alone at home.

The bullying presented it self in many ways. The usually “You’re ugly” and “You’re a nerd”, the later I’ve learned to embrace as a positive thing, was a mostly daily routine. But the occasional someone-tells-a-joke thing, was probably some of the worst. It would go like this: Some one tells a joke, everybody laughs, me included. Everybody turns to me and shouts “Why are you laughing”. Those things hit me the hardest, and has stuck with me ever since.

November ’98, 20 years ago, snow had fallen and the entire schoolyard and football field was covered in 10+ cm of snow. Btw. I love snow, always have, always will. And this story hasn’t changed that fact! For some reason, the school had decided that all pupils were not allowed to be indoors in the breaks, so we where all to go outside. There where one rule though: No snow tossing in the schoolyard, only on the football field. So, naturally, knowing my fellow students, I decided to stay in the schoolyard. For safety.

But, it didn’t take long before people came to me and started tossing snow at me. And it wasn’t even people from my own class, not even my year. It was people from the classes below AND above, that joined in on this fun game of covering the crying nerd in as much snow as possible. That was it. The teachers shrugged it away as “hormones” and “children will be children”. Less than one week after, on november 25th 1998, I started in the school where my grandmother and mother went to.

It was a private school, few pupils, looong waiting line. This being an emergency, and my family’s history with the school, it took one phone call and a I was out of hell!

I loved it there, no bullying, great teachers and awesome environment. Even though it took 2 hours a day on a bus just to get to school, I loved it there. Best 2½ years of my life at that point.

After that, not much happened. Experienced a bit of bullying later on, but as time went on I found my self in great company among other nerds and geeks making the bullying bearable. But the damage was still there.

At 18, I went to a psychologist for a few sessions because my then-girlfriends mother told me to, so I indulged her. The psychologist and I talked for a few sessions and in one of them he looked at me and told me these words exactly, well, he did say them in Danish, and it was 13 years ago:

I have never met, or heard of, one that has gone through so much hardship and still be alive to talk about it. You are the, mentally, strongest person I have ever met.

Those words has ever since been imprinted into my mind and to this day, has helped me a lot.

The years went on, and other than me having a few trust issues and some problems connecting to people and taking contact to people, I managed getting an education and a job. Yeah! I even bought my self a house!

Everything went OK, I began blogging quasi-seriously, stirring the Umbraco-pond on more than one occasion. I went to CodeGarden, became more and more active in my professional field as well as a volunteer at Odense Filmfestival. Everything was going the right way for me.

Bang! 2015 came along. I loathe 2015. This is the worst year of my adult life.

My company had just landed two major orders in Copenhagen, that meant that 4 developers was to go to Copenhagen every week for 6 months. That meant, back at the office we had: 1 full time designer, 2 or 3 interns and me.

As the senior, the only senior, everything went through me. Every small support task, every single task, went directly through me. No one else had the skillset that I had, so I had to coordinate a lot of things, on top of my own projects.

At first, I didn’t really notice it, but the projects I worked on began to fail. I almost couldn’t write a single line of code without it having bugs. This meant, frustrated customers, and a frustrated boss. When I began to notice this trend, I told my boss and colleagues, that I couldn’t handle anyting else. I could not work on more projects, I had reached my limit. I fact, I realized, I had crossed my limit. What was the reaction? “Just work longer”.

It became so bad at one point, that I have lost whole weeks that I cannot remember. I have no recollection of what I was doing or why. All I knew was I was at work, I could the se commits in our source control, but I could not remember ever doing them. I you haven’t tried it, that shit is scary as hell. What else had I forgotten?!

Summer 2015. I broke down. I, a grown ass man, was sitting in my chair in the office in front of my boss and colleagues, crying. At that point, it dawned on everyone what they had put me through. It also dawned on me, that I have been overworked for so long that something inside me has broken.

I began noticing something was off, I wasn’t myself anymore. I wasn’t normal. It was pointed out to me, unrelated, that I had som characteristics in common with Sheldon Cooper from the sitcom “The Big Bang Theory”. I knew, because I’m a nerd, that Sheldon is being portrayed as having Asperger’s Syndrome, a type of autism. So, I decided to see a psychiatrist.

We talked, for 5 hours, spread over 2 sessions, and he concluded that I did not have Asperger’s. Yeah f-ing yeah! I did, however, have semi-severe social anxiety with a mild depression caused by the massive bullying in school, buried for years and violently pulled back to the surface by the massive stress I’ve endured for 6-8 months.

But that didn’t mean the end of things. As I refuse to take any “mind altering drugs” like anti-depressives, I went to see a psychologist instead. We talked for 6-7 session, maybe more, can’t remember. And she gave me some tools and some guiding on “mindfulness” and meditation, that I have since done to some extend on a semi-regular basis whenever I feel stressed out.

It’s noteworthy, that through all of this, I had my company backing me up. They pulled people back from Copenhagen and accepted that I, at this point, still cannot work after hours. When I’m off, I’m off, and they support me 100%. Today, the slightest sign of stress, on anyone, is being handled up front. People are now, more than ever, encouraged to not work more than needed.

That year, 2015, caused so much havoc to my mind that I have ever since struggled doing what healthy people would do in a heartbeat. It can take anything from ½ an hour to several weeks for me to call someone on the phone. Sending an email, a text, even a comment takes a lot of effort for me to do. I cannot participate in online games without matchmaking, because I then have to engage with strangers. Doing tasks like calling for a plumber to fix the pipes in my house, also takes a lot of effort. Almost every socializing or interaction with other people is a struggle. Even writing this post.

I have so many posts in my head, and so many things I want to do but it takes a lot of effort for me to do at this point. But, as this post shows, I’m going in the right direction. One step at a time.

If you have been, or are, bullied as a kid or an adult. Talk to someone. Anyone. It will come back and do a lot of harm, just a matter of time. If you know someone that gets bullied, be that someone to talk to. You may save a life.

This is a long, personal, post that has taken 4 years to write, well 3 hours of actual writing, and is meant for me as a beginning to start blogging again and as a test of the changes I’ve made to my hosting environment, but more on that later.

Feel free to leave a comment, it should work, share on twitter, facebook, linkedin or whatever.

PSA: I am NOT in anyway, trying to harm myself. At all! Just to make sure you guys know that. After all this shit I’ve been through, I love my life, and I love living it. So don’t worry about me.