skip to content
Relatively General .NET

Adding .NET Aspire to your existing .NET apps

by Jon Galloway

posted on: August 07, 2024

.NET Aspire can really simplify local development for your existing apps, large or small. In this post, we'll look at how easy it is to make your current solutions better with just a few lines of code.

Simplifying [Theory] test data with Xunit.Combinatorial

by Andrew Lock

posted on: August 06, 2024

In this post I show how you can simplify your xUnit [Theory] tests using the Xunit.Combinatorial package to auto-generate your test parameters…

Legacy code with really good tests is still legacy code

by Oren Eini

posted on: August 05, 2024

I got into an interesting discussion on LinkedIn about my previous post, talking about Code Rot. I was asked about Legacy Code defined as code without tests and how I reconcile code rot with having tests.I started to reply there, but it really got out of hand and became its own post.“To me, legacy code is simply code without tests.” Michael Feathers, Working Effectively with Legacy CodeI read Working Effectively with Legacy Code for the first time in 2005 or thereabout, I think. It left a massive impression on me and on the industry at large. The book is one of the reasons I started rigorously writing tests for my code, it got me interested in mocking and eventually led me to writing Rhino Mocks. It is ironic that the point of this post is that I disagree with this statement by Michael because of Rhino Mocks. Let’s start with numbers, last commit to the Rhino Mocks repository was about a decade ago. It has just under 1,000 tests and code coverage that ranges between 95% - 100%. I can modify this codebase with confidence, knowing that I will not break stuff unintentionally. The design of the code is very explicitly meant to aid in testing and the entire project was developed with a Test First mindset. I haven’t touched the codebase in a decade (and it has been close to 15 years since I really delved into it). The code itself was written in .NET 1.1 around the 2006 timeframe. It literally predates generics in .NET. It compiles and runs all tests when I try to run it, which is great. But it is still very much a legacy codebase. It is a legacy codebase because changing this code is a big undertaking. This code will not run on modern systems. We need to address issues related to dynamic code generation between .NET Framework and .NET. That in turn requires a high level of expertise and knowledge. I’m fairly certain that given enough time and effort, it is possible to do so. The problem is that this will now require me to reconstitute my understanding of the code. The tests are going to be invaluable for actually making those changes, but the core issue is that a lot of knowledge has been lost. It will be a Project just to get it back to a normative state. This scenario is pretty interesting because I am actually looking back at my own project. Thinking about having to do the same to a similar project from someone else’s code is an even bigger challenge.Legacy code, in this context, means that there is a huge amount of effort required to start moving the project along. Note that if we had kept the knowledge and information within the same codebase, the same process would be far cheaper and easier.Legacy code isn’t about the state of the codebase in my eyes, it is about the state of the team maintaining it. The team, their knowledge, and expertise, are far more important than the code itself.An orphaned codebase, one that has no one to take care of, is a legacy project even if it has tests. Conversely, a project with no tests but with an actively knowledgeable team operating on it is not. Note that I absolutely agree that tests are crucial regardless. The distinction that I make between legacy projects and non-legacy projects is whether we can deliver a change to the system. Reminder: A codebase that isn’t being actively maintained and has no tests is the worst thing of all. If you are in that situation, go read Working Effectively with Legacy Code, it will be a lifesaver.I need a feature with an ideal cost of X (time, materials, effort, cost, etc). A project with no tests but people familiar with it will be able to deliver it at a cost of 2-3X. A legacy project will need 10X or more. The second feature may still require 2X from the maintained project, but only 5X from the legacy system. However, that initial cost to get things started is the killer.In other words, what matters here is the inertia, the ability to actually deliver updates to the system.

Create a bootable USB drive for Windows Server

by

posted on: August 05, 2024

The Windows Server image contains a .wim file which is bigger than 4GB. This is a problem because FAT32 does not support files bigger than 4GB. To solve this problem, we need to split the .wim file into smaller files. This can be done with the dism command. The following script extracts the content

Optimizing facets query performance in Corax

by Oren Eini

posted on: July 31, 2024

RavenDB allows you to query your data freely and cheaply. It is one of those things that makes or breaks a database, after all. After over a decade of working with Lucene as our backend indexing engine, we built Corax, a new querying & indexing engine that offers far better performance. Building an indexing engine is a humongous task. It took us close to ten years from the first line of code to Corax actually shipping. But I’m really happy with the way it turned out. Building a query engine is a big task, and we focused primarily on making the most common queries fast. The issue at hand is that RavenDB has many features, and we don’t have infinite time. So for the less common features, we typically implemented them as a straightforward port from whatever Lucene is doing. One such feature is facets. Let’s say that I want to buy a jacket. There are way too many choices, so I can use a faceted query to help me narrow it down.  Here is what this looks like in code:from Products where search(Description, "suit jacket") select facet(Brand), facet(Price < 200, Price between 200 and 400, Price between 400 and 800, Price > 800)And here is what this looks like as a website:I mentioned that we implemented some features as a straightforward port from Lucene, right?We did that because RavenDB offers very rich querying semantics, and we couldn’t spend the time to craft every single bit upfront. The idea was that we would get Corax out the door and be faster in most common scenarios, and at least at parity with everything else. It works for most scenarios, but not all of them. We recently got a query similar to the one above that was slower in Corax than in Lucene. That is usually good news since we have far more optimization opportunities in Corax. Lucene (and especially our usage of it) has already been through the wringer so many times that it is really hard to eke out any more meaningful performance gains. Corax’s architecture, on the other hand, gives us many more chances to do so.In the case of facets, the way Lucene handles that is roughly similar to this:def brand_facet(matches: List[int]): facet = dict() for term, docsForTerm in reader.terms("Brand"): facet[term] = count_intersect(matches,docsForTerm)Given the results of the query, run over all the terms for a particular field and intersect the documents for every term with the matches for the query. Lucene is able to do that efficiently because it materializes all its data into managed memory. That has costs associated with it:Higher managed memory usage (and associated GC costs)Slower initial queriesThe benefit of this approach is that many operations are simple, which is great. Corax, on the other hand, does not materialize all its data into managed memory. It uses persistent data structures on disk (leading to reduced memory usage and faster responses on the first query).The advantage we have with Corax is that the architecture allows us to optimize a lot more deeply. In this case, however, it turned out to be unnecessary, as we are already keeping track of all the relevant information. We just needed to re-implement faceted search in a Corax-native manner.You can see the changes here. But here is the summary. For a dataset with 10,000,000 records, with hundreds of brands to facet on, we get:Yes, that isn’t a mistake. Corax is so fast here that you can barely observe it 🙂.

Enhancing #help in F# Interactive

by David Schaefer

posted on: July 31, 2024

The '#help' directive in F# Interactive can now quickly access documentation instantly within the REPL.

Creating source-only NuGet packages

by Andrew Lock

posted on: July 30, 2024

In this post I show how you can create a NuGet package that contains source code (instead of dlls) which is then compiled into the target project…

With bugs, failures and errors: ever chugging forward

by Oren Eini

posted on: July 29, 2024

A customer called us about some pretty weird-looking numbers in their system:You’ll note that the total number of entries in the index across all the nodes does not match. Notice that node C has 1 less entry than the rest of the system. At the same time, all the indicators are green. As far as the administrator can tell, there is no issue, except for the number discrepancy. Why is it behaving in this manner? Well, let’s zoom out a bit. What are we actually looking at here? We are looking at the state of a particular index in a single database within a cluster of machines. When examining the index, there is no apparent problem. Indexing is running properly, after all.The actual problem was a replication issue, which prevented replication from proceeding to the third node. When looking at the index status, you can only see that the entry count is different. When we zoom out and look at the state of the cluster, we can see this:There are a few things that I want to point out in this scenario. The problem here is a pretty nasty one. All nodes are alive and well, they are communicating with each other, and any simple health check you run will give good results.However, there is a problem that prevents replication from properly flowing to node C. The actual details aren’t relevant (a bug that we fixed, to tell the complete story). The most important aspect is how RavenDB behaves in such a scenario.The cluster detected this as a problem, marked the node as problematic, and raised the appropriate alerts. As a result of this, clients would automatically be turned away from node C and use only the healthy nodes. From the customer’s perspective, the issue was never user-visible since the cluster isolated the problematic node. I had a hand in the design of this, and I wrote some of the relevant code. And I’m still looking at these screenshots with a big sense of accomplishment. This stuff isn’t easy or simple. But to an outside observer, the problem started from: why am I looking at funny numbers in the index state in the admin panel? And not at: why am I serving the wrong data to my users.The design of RavenDB is inherently paranoid. We go to a lot of trouble to ensure that even if you run into problems, even if you encounter outright bugs (as in this case), the system as a whole would know how to deal with them and either recover or work around the issue.As you can see, live in production, it actually works and does the Right Thing for you. Thus, I can end this post by saying that this behavior makes me truly happy.

Creating Bindings for .NET MAUI with Native Library Interop

by Rachel Kang (SHE/HER)

posted on: July 29, 2024

Learn how to get started creating bindings with Native Library Interop by following this example binding native Chart libraries in a .NET MAUI application.

Why you should use an AdBlocker?

by

posted on: July 29, 2024

Browsing web pages without an AdBlocker is a pain. You have to wait for the page to load, and then you have to close the popups, cookie banners, and ads. Some pages are unusable without an AdBlocker. I think it's a must-have for everyone. There are multiple reasons to use an AdBlocker:Reduce annoya