Basically, a dotnet solution is made up of one or more projects. Each of these projects have a project file, .csproj og .vbproj. If you’ve ever been working on a dotnet project, you’d already know this.
When we, the bureau I’m with, create new projects, we need to remember to add all sorts of configurations. All of them are shared between projects. Configurations like these:
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:
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.
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.
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.
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.
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:
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: