<![CDATA[ Exception Not Found ]]> https://exceptionnotfound.net https://exceptionnotfound.net/favicon.png Exception Not Found https://exceptionnotfound.net Tue, 19 Mar 2024 00:45:38 -0700 60 <![CDATA[ Re-thinking the Visitor Pattern with the Double-Dispatch Approach ]]> https://exceptionnotfound.net/rethinking-the-visitor-pattern-with-the-double-dispatch-approach/ 63f0339b65db5003c8aeb562 Wed, 22 Feb 2023 08:00:45 -0700 EDITOR'S NOTE: Hi y'all! Please welcome Huy Lương Vạn to the guest writer program! He is a first-time blogger and his article is below. Be nice! Comments are open at the end of the post. -Matthew

Have you ever heard about the Visitor Pattern – one of the Gang of Four Patterns? Have you tried researching it on the internet but still don’t know which problems it’s solving and which use cases should be applied in the real world. In this tutorial, I will show you a different approach called “Double Dispatch” to understand the Visitor pattern easier and how it is used in the .NET Library.

The Problem

When searching on the internet, I usually see this common definition:

“The Visitor pattern lets you define a new operation to a collection of objects without changing the objects themselves.”

I would like to say that it’s hard for

... Read more! ]]>
<![CDATA[ A Rant on the Occasional Inhumanity of Tech ]]> https://exceptionnotfound.net/a-rant-on-the-occasional-inhumanity-of-tech/ 6320ad21e9dee3cb6b93df3e Tue, 13 Sep 2022 09:47:49 -0700 As you know from my last post, my younger brother Aaron died of cancer around three months ago.

Aaron
Aaron is gone now. I miss him.

I don't want to rehash that whole thing here. Rather, this post is a rant about a comparatively minor thing that totally ruined my day, a thing where tech and grief intersect and make my life just that much more difficult. Part of my grieving process, you might say.

Aaron had a phone, a very nice Samsung s20 FE, and since he is gone we no longer have any use for it. Frankly, I want it out of my house; it's sat in my drawer as an ugly reminder of my loss for weeks now.

My sister-in-law (we'll call her T) needed a phone, so, with the permission of our parents, we are working on giving Aaron's phone to

... Read more! ]]>
<![CDATA[ Aaron ]]> https://exceptionnotfound.net/aaron-memoriam/ 62d6e259e9dee3cb6b93cdeb Tue, 19 Jul 2022 11:40:27 -0700 My younger brother Aaron died last month of pancreatic cancer. He was 33.

I'm writing this post for two reasons: one, so the world can help me remember him, and two, because I won't be able to write anything else ever if I can't get this out first.

I first want to re-publish something I wrote back in February, because it summarizes the feelings I had at the time that I don't think I can replicate, now that the end has already come.


My younger brother Aaron, my only sibling, is going to die of pancreatic cancer. This will most likely happen soon, within a year. He's only 33. And it's so goddamn unfair.

He was diagnosed last August, and it was a complete surprise. He went to the emergency room for abdominal pain, and after two days of tests, the doctors discovered an aggressive stage-4 pancreatic cancer that nobody

... Read more! ]]>
<![CDATA[ The Catch Block #104 - Thank You, Signing Off ]]> https://exceptionnotfound.net/the-catch-block-104-thank-you-signing-off/ 627154da126af8f60dd1a6b2 Wed, 04 May 2022 07:00:00 -0700 Dear Readers, it's time for me to do something else.

Eventually everything hits the bottom, and all you have to do is wait until someone comes along, and turns it back again. ⌛️
Photo by Aron Visuals / Unsplash

I have come to the difficult decision to cancel the publication of The Catch Block. This will be the last issue.

This was a hard decision to make, no doubt about it. I've been struggling with it for weeks. It was spurred on by two major life events, one you know about, and one I'll be writing about in the near future. Suffice to say, I have a lot happening all at once, though not all of it is bad.

At this moment, though, I don't feel that I can commit the energy and time to this publication that it deserves. You probably noticed that the last few issues haven't really been up to my normal standards. And I don't want to publish things that aren't up to my standards. I don't want to be

... Read more! ]]>
<![CDATA[ The Catch Block #103 - The Return of the Cool Read Extravaganza! ]]> https://exceptionnotfound.net/the-catch-block-103-the-return-of-the-cool-read-extravaganza/ 62671013126af8f60dd13eb5 Wed, 27 Apr 2022 08:54:26 -0700 Welcome to the 103rd edition of The Catch Block!

In this edition: there were a LOT of good reads this week, so let's check them out!

A golden retriever wearing glasses looks at the camera, while a book open to a page with a picture of a dog sits in front of him.
I'm not above using a cute dog in a photo. Photo by Jamie Street / Unsplash

Plus: a new .NET MAUI release candidate; F# for C# devs; aggregate roots; logical boundaries; and the Twitter sale. Let's go!

... Read more! ]]>
<![CDATA[ The Catch Block #102 - Microsoft Preview-palooza! ]]> https://exceptionnotfound.net/the-catch-block-102-microsoft-preview-palooza/ 625dad45126af8f60dd0dd0a Wed, 20 Apr 2022 08:47:14 -0700

This post is for paying subscribers only

Subscribe now

Already have an account? Sign in

... Read more! ]]>
<![CDATA[ The Catch Block #101 - On Feeling Stuck ]]> https://exceptionnotfound.net/the-catch-block-101-on-feeling-stuck/ 62546dc4126af8f60dd0820f Wed, 13 Apr 2022 07:00:00 -0700 Welcome to the 101st edition of The Catch Block!

In this edition: I'm feeling pretty stuck with my team's current application. What to do?

Survivor
Just squeeze a little more... Photo by Ben Hershey / Unsplash

Plus: pattern matching; unit tests; "Being Agile"; and better breadcrumbs.

Stuck in the Middle with You

The application I mentioned at the beginning of the edition is not a single app; it is comprised of a bunch of parts, including an internal web app, two distinct APIs, a public-facing web app, and a collection of smaller applications. One of the APIs is written in .NET Core 3.1, but everything else is .NET 4.8 or earlier. .NET 4.8 is the last version of .NET Framework, so no more upgrades are coming to that platform.

Obviously, I cannot speak too much about what exactly this application does, but I can say this: this application is critical to our company. It must be working 24/7, and issues in it cause the company to lose money and customers. There are bugs we must fix, as with any app, and the business team which owns it regularly requests improvements and changes.

These changes and bugs, as frequent as they are, make it difficult for my three-person team to pay down the app's technical debt, which has been accruing for over 10 years, and which we are only now making progress with.

In other words, my team's application is stuck. Stuck, and time-consuming to improve. I'm getting sick of it.

Spinning our wheels, making little progress. Photo by Aubrey Odom-Mabey / Unsplash

I've brought up this topic before, with three different bosses. This app needs to be moved to .NET 5 or higher if we're going to keep improving it. And each of my bosses has said the same thing: it sounds like a good idea, but not right now; we'll do it when we have time. And we never get time. Bugs and changes keep coming down the pipeline, and the refactoring needed to get the app ready is very slow going. Progress is progress, I know, but it's not going fast enough.

I understand the problem, I really do. This app is huge, with a lot of pieces, and the current refactoring work we're doing on it is really the first of many steps necessary to get it to a point where it even can be ported into .NET 5 or higher. The problem is huge, and from the business's perspective, might be a waste of money and time.

Yet, I'm sick of getting stuck, of being told "we'll do it later" when later never comes. I've been pushing to make certain changes, with moderate success, for a year now. Let's be real: the app is in a much better state than it was even a few months ago. It's much less brittle, meaning it is much easier to modify without causing a bunch of heretofore-unseen bugs.

Because we're using .NET 4.8, we're also stuck on C# 7.3. We're two major versions of C# behind, and by the end of this year, we will be three versions back. Maybe that doesn't matter in the big picture. But to me, it feels like we're falling behind and can't catch up. I know, logically, that's not the truth of it, but emotionally it feels like we're getting left in the dust.

... Read more! ]]>
<![CDATA[ The Catch Block #100 - Ridiculous Tech Interviewing Stories ]]> https://exceptionnotfound.net/the-catch-block-100-ridiculous-tech-interviewing-stories/ 624b41a7126af8f60dd003e2 Wed, 06 Apr 2022 08:50:47 -0700

This post is for paying subscribers only

Subscribe now

Already have an account? Sign in

... Read more! ]]>
<![CDATA[ Middleware in ASP.NET 6 - Conditionally Adding Middleware to the Pipeline ]]> https://exceptionnotfound.net/middleware-in-dotnet-6-conditionally-adding-middleware-to-the-pipeline/ 62290b0bf8508d63f8a8ea3e Mon, 04 Apr 2022 07:00:00 -0700 This is Part 4 of a four-part series. You might want to read Part 1, Part 2, and Part 3 first.

Welcome back! So far in this series, we've covered the basics of Middleware in .NET 6 applications, shown how to create custom Middleware classes, and discussed why the order of operations of the Middleware pipeline is so important.

In this final part of the series, we will show two ways to conditionally execute middleware in the pipeline: by using settings in the AppSettings.json file to determine whether or not to add the middleware to the pipeline in the first place, or by using the incoming request's data to conditionally execute a middleware piece already in the pipeline.

This way, or that way? Photo by Sigmund / Unsplash

Conditional Middleware based on AppSettings

Remember the TimeLoggingMiddleware class from Part 3? If you don't, here it is again.

using MiddlewareNET6Demo.Logging;
... Read more! ]]>
<![CDATA[ The Catch Block #99 - Finishing the Dapper Where Clause Builder ]]> https://exceptionnotfound.net/the-catch-block-99-finishing-the-dapper-where-clause-builder/ 623c99d2c818b78ea53bbea4 Wed, 30 Mar 2022 07:00:00 -0700 Welcome to the 99th edition of The Catch Block!

Let's put the finishing touches up! Photo by Rahul Bhogal / Unsplash

In this edition, we finish up the DapperWhereClauseBuilder that we started building two weeks ago.

Plus: gotchas; real-world refactoring; unit tests for legacy systems; the code review pyramid; and .NET 7 Preview 2.

Let's go!

Finishing the Dapper Where Clause Builder

In the previous issue, we talked at length about a class called DapperWhereClauseBuilder that we could use to build complex SQL WHERE clauses. At the end of the previous post, our class looked like this:

public class DapperWhereClauseBuilder
{
    private readonly SortedDictionary<string, object> singleValues = new SortedDictionary<string, object>();

    public DapperWhereClauseBuilder AddValue(string parameterName, int? value)
    {
        if (value.HasValue && !singleValues.ContainsKey(parameterName))
            singleValues.Add(parameterName, value.Value);

        return this;
    }
    
    //Lots of overload AddValue methods, one for each primitive type, including bool, double, string, DateTime, and more.

    public DapperWhereClauseBuilder AddValue(string parameterName, Guid? value)
    {
        if (value.HasValue && !singleValues.ContainsKey(parameterName))
            singleValues.Add(parameterName, value.Value);

        return this;
    }

    public string WhereClause
    {
        get
        {
            //If no values are submitted, there is effectively no WHERE clause.
            //So, return an empty string.
            if(!singleValues.Any())
                return string.Empty;

            string whereClause = " WHERE ";
            foreach(var item in singleValues)
            {
                whereClause += item.Key + " = @" + item.Key.ToLower() + " AND ";
            }
            whereClause = whereClause.Remove(whereClause.LastIndexOf("AND"));

            return whereClause;
        }
    }

    public DynamicParameters Parameters
    {
        get
        {
            DynamicParameters parameters = new DynamicParameters();
            foreach(var item in singleValues)
            {
                parameters.Add(item.Key.ToLower(), item.Value);
            }
            return parameters;
        }
    }
}

At this point, this class can only generate WHERE clauses for exact matches on a value. We could end up with "WHERE ColumnName = @Parameter". What we cannot do yet is have more complex matching clauses, such as IS NOT NULL or IS IN. We'll be extending DapperWhereClauseBuilder to permit these kinds of clauses in this issue.

IS NOT NULL Clauses

Let's deal with IS NOT NULL clauses first. We need a new collection in DapperWhereClauseBuilder to hold the column names that we need to check for NULL on:

public class DapperWhereClauseBuilder
{
    private readonly SortedDictionary<string, object> singleValues = new SortedDictionary<string, object>();
    
    //NEW
    private readonly List<string> notNullValues = new List<string>();
    
    //...Rest of implementation
}

We also need a method by which we can add columns to be checked for NOT NULL:

public class DapperWhereClauseBuilder
{
    //...Rest of implementation
    
    public DapperWhereClauseBuilder AddNotNull(string parameterName)
    {
        if (!notNullValues.Contains(parameterName))
            notNullValues.Add(parameterName);

        return this;
    }
    
    //...Rest of implementation
}

Finally, we need to modify the WhereClause property to includ the output for IS NOT NULL clauses:

... Read more! ]]>
<![CDATA[ Middleware in ASP.NET 6 - Order of Operations ]]> https://exceptionnotfound.net/middleware-in-dotnet-6-order-of-operations/ 62210179f8508d63f8a8996f Mon, 28 Mar 2022 07:00:00 -0700 This post is Part 3 of a four-part series. You might want to read Part 1 and Part 2 first.

Let's continue our series on Middleware in .NET 6 by discussing the pipeline created by Middleware, and specifically why the order in which the middleware are added to the pipeline is very important.

You should know the order of things BEFORE this point. Photo by SELİM ARDA ERYILMAZ / Unsplash

The Sample Project

As with all of my code-focused posts, there's a demo project that demonstrates the ideas in this post over on GitHub. You can check it out here:

GitHub - exceptionnotfound/MiddlewareNET6Demo
Contribute to exceptionnotfound/MiddlewareNET6Demo development by creating an account on GitHub.

Order of Operations

Recall from Part 1 of this series that middleware forms a pipeline, and the middleware in that pipeline are executed in a certain order, an example of which is shown in

... Read more! ]]>
<![CDATA[ Middleware in ASP.NET 6 - Custom Middleware Classes ]]> https://exceptionnotfound.net/middleware-in-asp-net-6-custom-middleware-classes/ 621ea1dcf8508d63f8a8693d Mon, 21 Mar 2022 07:00:00 -0700 This post is part 2 of a four-part series. You might want to read Part 1 first.

In the last post, we talked about what Middleware is, what it's for, and simple ways of including it in our ASP.NET 6 app's pipeline.

Hydro-electricity piplines above Tarraleah Power Station, Tarraleah, Tasmania.
See, now you get why I'm including pictures of pipelines. Photo by Christian Bass / Unsplash

In this post, we're going to expand on these fundamentals to build a few custom Middleware classes.

The Sample Project

Don't forget to check out the sample project over on GitHub:

GitHub - exceptionnotfound/MiddlewareNET6Demo
Contribute to exceptionnotfound/MiddlewareNET6Demo development by creating an account on GitHub.

The Standard Middleware Architecture

Unlike what we did in Part 1, most of the time we want to have our Middleware be separate classes, and not just additional lines in our Program.cs file.

Remember this middleware from Part 1? The one that returned

... Read more! ]]>
<![CDATA[ The Catch Block #98 - Dapper Where Clause Builder ]]> https://exceptionnotfound.net/the-catch-block-98-dapper-where-clause-builder/ 622f6df77cdc1959e3e304d4 Wed, 16 Mar 2022 07:00:00 -0700 Welcome to the 98th edition of The Catch Block!

In this edition, we build a class that can generate relatively-complicated SQL WHERE clauses from a group of parameters.

A builder measures out the gap between each beam
And we won't even need a tape measure! Photo by Callum Hill / Unsplash

Plus: you should deploy more; feature flags; designing better APIs; cancellation; and VS's 25th anniversary!

Dapper Where Clause Builder

My team and I made the commitment to use Dapper entirely for our major project, and so far it's going pretty well. But occasionally we are running across things like enormously complicated queries, which Entity Framework can do fairly simply but Dapper has a harder time with.

Queries such as this, from one of our pages that has a bunch of search fields:

public List<LocationDetail> SearchForLocations(LocationSearchVM viewModel)
{
    var resultList 
        = _locationEntities.PickupLocations
          .Where(x => viewModel.Id == null || x.id == viewModel.Id)
          .Where(x => viewModel.LocationID == null || x.locationid == viewModel.LocationID)
          .Where(x => viewModel.LocationName == null || x.locationname == viewModel.LocationName)
          .Where(x => viewModel.Phone == null || x.phone == viewModel.Phone)
          .Where(x => viewModel.Address1 == null || x.address1 == viewModel.Address1)
          .Where(x => viewModel.Address2 == null || x.address2 == viewModel.Address2)
          .Where(x => viewModel.PostalCode == null || x.postalcode == viewModel.PostalCode)
          .Where(x => viewModel.Active || !x.active)
          .Take(1000)
          .ToList();
}
Ignore that this doesn't compile, please.

Notice that this particular query has to do a null check, and if the property in question is null, the item gets included in the result set.

This is a terribly complicated query at first glance, but peer a little closer and you start to see the similarities. I decided that it might be possible to create a class which generates the above code as a SQL WHERE clause, and parameters, for Dapper to consumer. Turns out, it IS possible.

In this post and the next (which will come out in two weeks, see below), we'll show how to build the DapperWhereClauseBuilder class for a few types of queries, and how to use it to replace the above query. Let's go!

The Internals of DapperWhereClauseBuilder

The DapperWhereClauseBuilder class needs to do the following things:

  1. Accept a string parameter name and a value for any given parameter.
  2. Accept "is not null" parameters.
  3. Accept "is in this collection" parameters.
  4. Generate the WHERE clause that results from all the given parameters.

We're going to do 1 and part of 4 in this post, and we'll do the others in the next one.

So, let's start with a new DapperWhereClauseBuilder class and an internal collection of parameters, which we will make a SortedDictionary<string, object> type:

public class DapperWhereClauseBuilder
{
    private readonly SortedDictionary<string, object> singleValues = new SortedDictionary<string, object>();
}

We now need accessor methods to add parameters of different types to the where clause builder. Let's start with a simple one, for int:

public class DapperWhereClauseBuilder
{
        private readonly SortedDictionary<string, object> singleValues = new SortedDictionary<string, object>();

    public DapperWhereClauseBuilder AddValue(string parameterName, int? value)
    {
        if (value.HasValue && !singleValues.ContainsKey(parameterName))
            singleValues.Add(parameterName, value.Value);

        return this;
    }
}

Note that we're doing two kinds of checks on the passed-in value: confirming that the nullable value actually has a value, and confirming that the value parameterName does not already exist in our dictionary of parameters. We also made the method return DapperWhereClauseBuilder so it can be used in a fluent manner.

We will need quite a few of this basic AddValue() methods; here's two more:

public class DapperWhereClauseBuilder
{
    //...Rest of implementation

    public DapperWhereClauseBuilder AddValue(string parameterName, double? value)
    {
        if (value.HasValue && !singleValues.ContainsKey(parameterName))
            singleValues.Add(parameterName, value.Value);

        return this;
    }

    public DapperWhereClauseBuilder AddValue(string parameterName, long? value)
    {
        if (value.HasValue && !singleValues.ContainsKey(parameterName))
            singleValues.Add(parameterName, value.Value);

        return this;
    }
}

For the majority of these methods, the logic is the same even as the type of the second parameter changes: ensure that the value is not null, and ensure that the parameterName does not already exist in the dictionary. The implementation of that logic changes, very slightly, when we get to adding a value of type string:

... Read more! ]]>
<![CDATA[ Middleware in ASP.NET 6 - Intro and Basics ]]> https://exceptionnotfound.net/middleware-in-asp-dotnet-6-intro-and-basics/ 621e72b9f8508d63f8a865d5 Mon, 14 Mar 2022 07:00:00 -0700 Welcome, dear readers, to a brand new series about middleware in .NET 6!

We're going to talk about what middleware is, what it does, why we use it, and demo several implementations of various kinds of middleware. We'll also talk about the pipeline that middleware exists in, how to create it, and why the order of operations in that pipeline matters. Finally, we'll even show two ways to conditionally execute middleware in the pipeline to give you a finer-grain control of what your app does.

Old rusty waterpipe near Melitopol'
Photo by Rodion Kutsaev / Unsplash

Let's get started!

The Sample Project

As with all of my code-focused posts, there's a sample project hosted on GitHub that demonstrates the ideas in this post. You can check it out here:

GitHub - exceptionnotfound/MiddlewareNET6Demo
Contribute to exceptionnotfound/MiddlewareNET6Demo development by creating an account on GitHub.

Middleware Basics

At its most fundamental, any given interaction using

... Read more! ]]>
<![CDATA[ The Catch Block #97 - The Old, Familiar Comments Trap ]]> https://exceptionnotfound.net/the-catch-block-97-the-old-familiar-comments-trap/ 622655f8f8508d63f8a8c79e Wed, 09 Mar 2022 07:00:00 -0700

This post is for paying subscribers only

Subscribe now

Already have an account? Sign in

... Read more! ]]>