A388

Arex388.Geocodio 1.2.2 Released

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

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.

ASP.NET Core Middleware for HTML Minification

Today I was reading a blog post by Jeremy Lindsay about an ASP.NET Core middleware to prettify HTML output. It piqued my interest because I use a similar middleware in my projects to minify HTML. Looking through his code and comparing it to mine, they're very similar with maybe the biggest difference being that he uses AngleSharp and I use HtmlAgilityPack.

I have not tested his solution, but I have no doubt it works. My only concern is the use of the inner StreamReader, but it may be because of AngleSharp input requirements. I haven't used AngleSharp either so I can't say.

I do know that two things that bit me in the butt with my minifier were not applying the correct encoding when loading the source HTML, and not filtering out non-text/html responses.

Arex388.AspNetCore NuGet Package

I was using my minifier across multiple projects, so I ended up recently extracting it out into a NuGet package with other ASP.NET Core extensions I use. The NuGet package is called Arex388.AspNetCore, and the source code is available on GitHub.

For those interested in only the code, as it is at the time, I write this, here it is:

HtmlMinifierMiddleware
public sealed class HtmlMinifierMiddleware {
	private RequestDelegate Next { get; }

	public HtmlMinifierMiddleware(
		RequestDelegate next) => Next = next;

	public async Task InvokeAsync(
		HttpContext context) {
		var response = context.Response;
		var stream = response.Body;

		using (var memoryStream = new MemoryStream()) {
			response.Body = memoryStream;

			await Next(context);

			memoryStream.Seek(0, SeekOrigin.Begin);

			if (response.ContentType.Contains("text/html")) {
				var document = new HtmlDocument();

				document.Load(memoryStream, Encoding.UTF8);
				document.DocumentNode.TrimWhitespace();
				document.Save(stream);
			}

			await memoryStream.CopyToAsync(stream);

			response.Body = stream;
		}
	}
}
HtmlNodeExtensions
public static class HtmlNodeExtensions {
	public static IList<HtmlNode> SelectNodesAsList(
		this HtmlNode node,
		string xpath) {
		return node.SelectNodes(xpath)?.ToList() ?? new List<HtmlNode>(0);
	}

	public static void TrimWhitespace(
		this HtmlNode document) {
		var textNodes = document.SelectNodesAsList("//text()").Where(
			n => string.IsNullOrWhiteSpace(n.InnerHtml));

		foreach (var textNode in textNodes) {
			textNode.Remove();
		}

		var commentNodes = document.SelectNodesAsList("//comment()").Where(
			n => n.InnerHtml != "<!DOCTYPE html>");

		foreach (var commentNode in commentNodes) {
			commentNode.Remove();
		}
	}
}
HtmlMinifierMiddlewareExtensions
public static class HtmlMinifierMiddlewareExtensions {
	public static IApplicationBuilder UseHtmlMinifierMiddleware(
		this IApplicationBuilder builder) {
		return builder.UseMiddleware<HtmlMinifierMiddleware>();
	}
}

I usually add it to the Startup class right above the app.UseMvc(... registration. I'll write a follow up post about why I do HTML minification.

Arex388.Geocodio is Now Listed as a Library on Geocod.io

Some belated, but happy news I forgot to mention is that I requested and was approved for my Geocodio C# library to be included in the list of libraries on Geocod.io's documentation page. It feels pretty nice to work on something for an extended period of time, to share it, and to be allowed to list it officially. Hopefully, other Geocod.io users that are using .NET/C# can take advantage of and be helped by the library. A big thank you to Geocod.io for letting me on the list!

Arex388.Geocodio 1.2.0 Released

This update includes backwards compatibility support. The GeocodioClient now accepts a third argument for the endpoint version. By default the client will always use the latest version, but it can be changed using the new EndpointVersions constants.

Arex388.Geocodio 1.1.0 Released

This update includes full support for all fields and their return values. Originally, I had implemented the lookup for fields, but I omitted the return values for some reason. Since Geocodio had an update to the fields I decided to rectify this omission. Please be aware that field lookups will result in additional charges from Geocodio.