A388

Review: Star Wars - Thrawn Trilogy Series

I recently finished listening to the Star Wars: Thrawn Trilogy audiobook series, and I quite liked it. At times I didn‘t do anything but sit and listen to it, that‘s how captivated I was with it.

Going into it I didn‘t know much about Thrawn except that he was a tactical genius and that he was the "bad guy". Now, having completed the series, I find him to be quite likable. He‘s still the "bad guy", but he’s the lovable bad guy. Also, yes, he is a tactical genius. His ability to piece together the big picture out of bread crumbs is amazing.

I think this series also introduced Talon Karrde, whom I already liked, but didn‘t know much about either. He‘s Thrawn‘s "good guy" counterpart in my opinion, even though he resists fully committing to being that.

Mara Jade is also introduced, I think, but she‘s kind of mean, although that‘s because she was still under the influence of Palpatine. I‘m glad she finally breaks out of it, although I have a feeling that Palpatine‘s influence was to undermine Joruus C‘baoth‘s plan to claim the Empire as his own.

Overall I really enjoyed the series and I highly recommend it to any Star Wars fan.

Arex388.Geocodio 1.2.2 Released

This is a minor update to add the Metropolitan Divisions property for Census data.

Review: Star Wars - X-Wing Series

Since I recently posted a review of the Michael Vey book series, I decided to write a short review of the Star Wars: X-Wing book series since I finished it first. This was the book series that I used to start listening to audiobooks when driving to and from work. I really like Star Wars and specifically Legends (because new cannon is weak, to say the least...) so I started out with the X-Wing series.

I quite enjoyed the series and learned about the campaigns to capture Coruscant, against Ysanne Isard and the capture of the Lusankya SSD, and the defeat of Warlord Zsinj, amongst other events. Having subscribed to EckhartsLadder YouTube channel and watched his videos on those events I had a rough idea of what happened, but I still liked going through the books. Sadly, I think the audiobooks were abridged, so I probably missed on quite a bit of detail. Maybe one day I'll actually read through the books and see, but that requires spare time...

As I was getting the list of the book titles for this post, I saw that there is a book that I haven't listened to yet. X-Wing: Mercy Kill, listed as a side novel to the Fate of the Jedi series, which I haven't gone through yet, so I'll wait until then.

Anyway, if you like Star Wars and Legends, then I highly recommend reading the books or listening to the audiobooks.

Cleanly Optimize and Embed Font Awesome in Your Website

This is a sort of a follow up to the Website Font Optimization post I wrote a while back, but it would still be beneficial because sometimes I forget how to properly embed Font Awesome (or any font) in a website even though I've done it correctly before.

I was embedding Font Awesome in a new project I'm working on and I started messing around with CSS rules to get the glyphs to actually display and I was thinking to myself that there should be an easier way to do this. Turns out there is, and I was using that on a different project that I happened to update over the last couple of days and it reminded me how to do it properly.

Follow the instructions from the Website Font Optimization post to get your final WOFF2 font. Base64 encode and embed it into your CSS if you want to, I usually do. Here's the crucial step. By default you would use Font Awesome by writing this: <i class="fa fa-font-awesome-flag"></i>, and you still can with my method, except you would copy the glyph into the content of the <i> tag like: <i class="fa"></i>.

When you read this, it will probably show as a blank space, but the glyph is actually there. Then in your CSS, you would simply write a rule for .fa to have font-family: FontAwesome. This way you don't have to mess around with ::before or ::after rules to fill in the glyph since you're bypassing the usual way to use Font Awesome.

Make sure that when you're optimizing your font that you update the character code because if IcoMoon doesn't recognize it it will generate a new one and the glyphs will not show up. I usually just go into the icon details on Font Awesome's website and copy the code from there. I also use the IcoMoon pre-download page to copy the glyphs out. Here's a more complete example of how I do it.

CSS

@font-face {
    font-family: FontAwesome;
    font-display: swap;
    src: url("data:font/woff2;base64,...base64_encoded_font...");
}

.fa {
    font-family: FontAwesome;
    font-style: normal;
}

HTML

<a>
    <i class="fa"></i>
    <span>Font Awesome is awesome!</span>
</a>

Hope this helps you, and if nothing else, it will help me to remember in the future how to do it easily.

Review: Michael Vey Book Series

I've been wanting to add non-programming content to my blog a little while now, and now I have something to do so with. It's a little late since I finished the series a few weeks ago, but I wanted to do a short review of the Michael Vey series of books by Richard Paul Evans.

I originally learned about the series when Mr. Evans did an interview with Glenn Beck years ago when he was releasing the first book, Michael Vey: The Prisoner of Cell 25. I purchased the book, read a few of the beginning parts, and then I put it on my bookshelf. I didn't dislike the book, but it was a little slow in the beginning and I just had quite a bit going on at the time in real life and couldn't dedicate the time to read it.

Then, about January of this year (2019 as I'm writing this), I decided to start listening to audio books while I'm driving since I spend an hour each way to and from work. I actually started out with a different series, which I might review as well, but when I finished it I decided to start the Michael Vey series again.

Like the first time when I was reading the physical book, the beginning was a little slow, but eventually, it picked up the pace and never slowed down. I'm not going to spoil anything from the books because I really enjoyed the story and would like you to as well.

There are many emotions throughout the story from anger to happiness, sadness, and sorrow to satisfaction. A sad moment occurred around the middle of the series and I got quite teary eyed. Other times I wanted to slap the main character for falling into the psychological traps of the villain.

I did learn quite a bit from the books though and have found myself using that knowledge in day-to-day conversations. There's a very satisfying feeling when that happens.

Overall, I consider this one of my favorite book series and my only sadness is that it's over. I highly recommend reading the books or listening to the audiobooks. The audiobooks themselves were very well done. I would be very happy if this was made into a TV series since there's too much to fit into a movie series.

You can purchase the books from Amazon or the audiobooks from Audible (of course, from other retailers as well).

Projection-Result Pattern: Improving on the Projection-View Pattern...

If you haven't already, I would recommend you read through my original post on the Projection-View Pattern first even though this post is technically a prequel to it. While that pattern is still fine, and I still continue to use it and I will not be changing that, I found myself wanting to use it in non-view workloads.

Specifically, in my project at work, I have quite a lot of code that needs to project data out of the database and transform it, but it never returns a view to the screen. It might be sending off an email, or generating a file, outputting JSON, or something else entirely.

I used to just adapt to what I needed at the time and inline my projections, but it felt highly disconnected considering I had been using the Projection-View Pattern with great results and success for my views. Then it kind of hit me that the view was nothing more than a result of the projection, and the result could be anything, not just a view.

So, I decided to step back a moment and after some more thought and tinkering, I settled on a base pattern that I've dubbed the Projection-Result Pattern. You're amazed at my naming skills, I know...

Also, since the original post for the Projection-View Pattern, I've changed up how I use MediatR, and I now implement IRequestHandler<TRequest, TResponse> instead of HandlerBase<TRequest, TResponse>. I'll show the entire stack of base classes as I am currently using them:

  • AsyncHandlerBase<TRequest> is a simple handler that doesn't return a result.
  • AsyncHandlerBase<TRequest, TResponse> is a simple handler that does return a result.
  • AsyncProjectionHandlerBase<TRequest, TProjection, TResult> is a slightly more complex handler that does data projection and returns a result.
  • QueryHandlerBase<TQuery, TProjection, TView> is an optional handler, but it has its place when I'm returning views. As I'm writing this I'm starting to think that maybe it should be called something different like ViewHandlerBase<TQuery, TProjection, TView> to express it's intent more clearly, but I'll have to sleep on it for a bit.

Now let's look at the code for each of them. For the purpose of the example, we'll pretend there's a DbContext class called MyDbContext that's being injected. Of course, you may not need to inject anything or you just want to use the built-in handler base classes, and that's perfectly fine, I'm just showcasing how I've decided to use MediatR.

AsyncHandlerBase<TRequest>

public abstract class AsyncHandlerBase<TRequest> :
    IRequestHandler<TRequest>
    where TRequest : IRequest {
    protected MyDbContext Context { get; }
    protected IMapper Mapper { get; }

    protected AsyncHandlerBase(
        MyDbContext context,
        IMapper mapper) {
        Context = context;
        Mapper = mapper;
    }

    public abstract Task<Unit> Handle(
        TRequest request,
        CancellationToken cancellationToken = default);
}

AsyncHandlerBase<TRequest, TResponse>

The AsyncHandlerBase<TRequest, TResponse> class has an additional property called MapperConfig which I pass to the .ProjectTo<T>() AutoMapper extension methods when I need to. It's slightly less typing than the full Mapper.ConfigurationProvider everywhere. You can remove it if you don't like it or want it.

While I've debated on putting the MapperConfig property in the AsyncProjectionHandlerBase<TRequest, TProjection, TResult> class only, I decided against it because you may end up using the projection extension methods in a class inheriting from this one as well.

public abstract class AsyncHandlerBase<TRequest, TResponse> :
    IRequestHandler<TRequest, TResponse>
    where TRequest : IRequest<TResponse> {
    protected MyDbContext Context { get; }
    protected IMapper Mapper { get; }

    protected IConfigurationProvider MapperConfig => Mapper.ConfigurationProvider;

    protected AsyncHandlerBase(
        MyDbContext context,
        IMapper mapper) {
        Context = context;
        Mapper = mapper;
    }

    public abstract Task<TResponse> Handle(
        TRequest request,
        CancellationToken cancellationToken = default);
}

AsyncProjectionHandlerBase<TRequest, TProjection, TResult>

public abstract class AsyncProjectionHandlerBase<TRequest, TProjection, TResult> :
    AsyncHandlerBase<TRequest, TResult>
    where TRequest : IRequest<TResult>
    where TProjection : class, new()
    where TResult : class {
    protected AsyncProjectionHandlerBase(
        MyDbContext context,
        IMapper mapper)
        : base(context, mapper) {
    }

    public override Task<TResult> Handle(
        TRequest request,
        CancellationToken cancellationToken = default) {
        var result = GetResult(request);

        return Task.FromResult(result);
    }

    protected virtual TProjection GetProjection(
        TRequest request) {
        return new TProjection();
    }

    protected virtual TResult GetResult(
        TRequest request) {
        var projection = GetProjection(request);
        var result = Mapper.Map<TResult>(projection);

        NormalizeResult(request, projection, result);

        return result;
    }

    protected virtual void NormalizeResult(
        TRequest request,
        TProjection projection,
        TResult result) {
    }
}

QueryHandlerBase<TQuery, TProjection, TView>

Lastly, we have the QueryHandlerBase<TQuery, TProjection, TView> class, which is optional, but I've found it useful since I only use it to return views and I usually need to have access to the user identity. In this example, I'm also injecting the IdentityProvider from my Arex388.AspNetCore NuGet package. I also constrain the TProjection and TView parameters to inherit from the ProjectionBase and ViewBase base classes.

public abstract class QueryHandlerBase<TQuery, TProjection, TView> :
    AsyncProjectionHandlerBase<TQuery, TProjection, TView>
    where TQuery : IRequest<TView>
    where TProjection : ProjectionBase, new()
    where TView : ViewBase {
    protected IdentityProvider Identity { get; }

    protected QueryHandlerBase(
        MyDbContext context,
        IMapper mapper,
        IdentityProvider identity) => Identity = identity;
}

Summary

With all of that code, I'm wrapping up this post. Really, the Projection-Result Pattern should be thought of as a generic version of the Projection-View Pattern, but it doesn't replace it. Both patterns have their places and use. The Projection-View Pattern simply specializes the Projection-Result Pattern when dealing with returning views to the user.

I hope either pattern will help someone in their coding, they've surely helped me in improving my data access as well as standardizing on a common coding pattern. Between the two patterns, I probably have about 250 or more classes that inherit from them in my work's project.

Arex388.AspNetCore 1.0.18 Released

I've been adding new functionality to the Arex388.AspNetCore package since the last post, and have published a new update. Rather than rehashing what I already wrote in the README on the GitHub repository, I'll just direct you there. I'm happy to say that it's coming along nicely and it lets me learn how to do more things with ASP.NET Core.

Arex388.AspNetCore 1.0.11 Released

As I've progressed in my usage and understanding of ASP.NET Core, I decided to make me a small support library for shared functionality between projects. From the beginning, I released it as a NuGet package, and while I didn't hide it, I also didn't promote it.

Well, I'm changing that. With the latest release, I feel pretty confident in it and would like to share it with the world. It's called Arex388.AspNetCore (bet you didn't see that coming) and it's currently at version 1.0.11. The source code is available on GitHub and the package is available on NuGet.

AuthenticatedStaticFilesMiddleware

The AutheticatedStaticFiles middleware is a "security" middleware that intercepts requests to static files and checks if the user has been authorized to access the app. If the user is not authorized, then return a 404 response.

public void ConfigureServices(
	IServiceCollection services) {
	services.Configure<AuthenticatedStaticFileOptions>(
		o => {
			o.Paths = new List<string> {
				"admin.min.js",
				"admin.min.css"
			};
		});
}

public void Configure(
	IApplicationBuilder app) {
	//  ...

	app.UseAuthentication();
	app.UseAuthenticatedStaticFiles();
	app.UseStaticFiles();

	//  ...
}

FeaturesViewLocationExpander

A view location expander that follows the Vertical slices architecture style by Jimmy Bogard.

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

HtmlMinifierMiddleware

A middleware to minify HTML after the Razor view has been rendered using the HtmlAgilityPack.

public void Configure(
	IApplicationBuilder app) {
	//  ...

	app.UseHtmlMinifier();
	app.UseMvc(...);

	//  ...
}

SimpleSlugifyParameterTransformer

A parameter transformer to slugify a route value. It is very simple in that it assumes that action names are camel cased, which it then kebaberizes.

public void ConfigureServices(
	IServiceCollection services) {
	services.Configure<RouteOptions>()
		.ConfigureSimpleSlugifyParameterTransformer();
}

SitemapMiddleware

A placeholder middleware to intercept a request for a sitemap and generate one. You will have to implement your own concrete SitemapMiddleware with your own logic for generating the sitemap. Every app is different so your way of generating the sitemap will vary depending on your data sources.

public void Configure(
	IApplicationBuilder app) {
	//  ...

	app.UseSitemap<SitemapMiddleware>();

	//  ...
}

Arex388.SunriseSunset 1.0.0 Released

I'm starting a new project, and it has a requirement to be able to pull the sunset time for each day. I looked around a bit for an API and found that sunrise-sunset.org has such an API.

I figured I might as well add to my collection of API clients, so I made a C# client for it. It's called Arex388.SunriseSunset, as you might have guessed by now.

The source code is available on GitHub, and a package is available on NuGet.

Why Should HTML be Minified?

In my last post, I wrote about an HTML minification middleware I use in my ASP.NET Core projects. In this post, I want to write about why HTML should be minified. Most likely, with everything else that's minified nowadays, you may be thinking it's for reduced network transfer and increase performance. You're right, but that's not all. In fact, to me, that's not even the most important reason.

The most important reason is CSS. When HTML is minified, and its whitespace is removed, it makes it much easier to style with CSS. If you don't remove whitespace, then it causes annoying styling issues by altering the dimensions of an element on the screen and bumping other elements around. You then must do weird trickery within your stylesheets to correct for that.

It's much easier to simply not worry about that AND get the benefit of lower network transfer and higher performance. Pretty simple, eh?

If you're using ASP.NET Core and are interested in HTML minification, then I recommend giving my middleware a try. You can find it on NuGet under Arex388.AspNetCore. That being said, while I don't intend for it to be volatile, it may be because it was spawned from my need to share code between my projects and as such as my needs change so will it. The HTML minification middleware will always be there though.