- Home>
- C#
I have been a C#/.NET developer for nearly ten years. Before that, I was a Java/Spring Framework developer for a couple of years. As with every change in technology, it took a bit of time to catch up and get comfortable in the new environment. However, the move from Spring Framework to .NET was an upgrade for me personally in terms of developer satisfaction. For instance, I was using Java 7 or 8, and the corresponding version of C# felt slightly more modern to me. I enjoyed using C# delegates, which I don’t believe had an equivalent in Java at the time, to build reusable code.
In addition, it’s easier to get started with good design patterns like dependency injection in .NET, protect API endpoints using middleware, and start web apps. I specifically like how .NET uses a code-first approach to configure dependencies using code instead of annotations. In comparison, using Spring Framework at the time, I would need to decorate service classes to use them as dependencies. I also like how easy it is to add NuGet packages in .NET compared to adding libraries in Java.
Fast forward to today, I feel at home and comfortable with all the great features that .NET has provided. For instance, it’s easy for me to set up a new .NET project with clean architecture, Entity Framework, Microsoft Authentication Library (MSAL), MediatR, etc., and get a solid starter codebase to build great apps. I especially enjoy how the .NET team and Microsoft strive to enable .NET developers to go full-stack with .NET, with frameworks like Blazor — which I myself am using to build user interfaces.
As someone who cares about clean code, I like how .NET and C# allow developers to implement layered architecture or clean architecture by simply creating different projects for different layers and controlling dependencies between them more robustly than just using folders in the same project. This is especially useful if you want to implement clean architecture. For example, using different projects for each layer allows me to enforce that the domain layer does not have dependencies on other projects by simply not adding those projects as dependencies. If I have just one project and implement different layers in separate packages or directories, it’s harder to ensure that code in the domain layer does not reference code in other folders or layers.
Being an object-oriented language, I enjoy developing in C# and using its object-oriented features to implement well-established design patterns. For example, the Command Query Responsibility Segregation (CQRS) pattern is a good way to adhere to the Single Responsibility Principle. Essentially, a single responsibility can be encapsulated into a command or query, with its corresponding implementation in a handler. In C# or similar object-oriented languages, you can define the command or query in an interface or abstract class, and the handler in a concrete class. The MediatR library makes it seamless to register queries, commands, and their corresponding handlers so you can use them in service classes by simply executing commands or queries, with the library handling the appropriate lookup.
Regarding the open-closed principle, the idea is to allow extending the functionality of a class or module without modifying the class. One way C# helps to implement this pattern is via interfaces. For example, suppose you have a class that utilizes Redis for caching. Instead of injecting this class directly into other classes, you can create an interface that defines the necessary caching methods and have the service implement this interface. Then, you inject an instance of the interface, not the concrete class. This approach aligns with the open-closed principle because it allows you to extend caching behavior at runtime by injecting a different implementation of the interface, without changing the classes that depend on it.
Last but not least, I like the user interface in Visual Studio for managing packages. It’s easy to search for and add packages and libraries. Back when I was focusing on Java codebases a few years ago, I used Maven to manage Java libraries. To add a library, I had to know and specify the group ID, artifact ID, version, and scope. To me, dependency management in Java just felt less intuitive and harder to use compared to .NET, thanks to the available tooling and IDE support.
I’m pleased that Microsoft and the .NET team have been consistently releasing updates that continue to improve .NET and C#, as well as enhance the developer experience. At the same time, I’m also excited to build applications in other languages, particularly Python. In fact, I’ve recently been focusing on building apps in Python. I felt reluctant and awkward at first because I was so used to writing code in C# and enjoying all the benefits of .NET. But I’m getting better at it and plan to share more in upcoming posts — particularly about the techniques and tools I use to organize the codebase and write code that meets my standards of cleanliness and maintainability.
Since the advent of Large Language Models (LLMs), we have been leveraging the technology to streamline processes and improve staff efficiency. One of the things LLMs are good at is coming up with coherent text based on a given context. Leveraging this strength, we have applied the Retrieval Augmented Generation (RAG) pattern to build chatbots that help staff across different areas, including human resources, contract management, and other department-specific procedures. For the user interface of the chatbots, we use Microsoft Teams, as it’s our primary communication platform. The bots’ functionalities are quite similar in nature, in the sense that they all pull data from a data store by calling APIs, sending the user’s chat message, and replying to messages in Microsoft Teams. However, because each bot requires a different category of data for use as the LLM’s context, we need a separate Microsoft Teams app for each bot. Behind the scenes, the Teams app calls the corresponding web API, which is an ASP.NET Core application, to send and receive messages. The ASP.NET Core applications are very similar in nature, in that they all call APIs to get the LLM’s response for a user’s request. As such, we were thinking of ways to minimize code duplication and the infrastructure needed to support multiple bots. In the web API, we follow a clean architecture with infrastructure, a shared kernel, a domain layer, and other typical layers. One approach I thought of was that we could reuse all the layers except for the web API. We would have one web API project for each bot we want to support. However, that approach still requires separate infrastructure for each bot—things like Key Vault, App Insights, Blob Storage, etc.—since each bot is a separate app that serves a different domain. Luckily, after some back-and-forth discussions with GitHub Copilot and reading sample code, I tested and found a way to support multiple bots using the same codebase and infrastructure.
Continue readingIn this post, I write about building an API to help me analyze real estate properties.
Continue readingIn this post, I show an example of scraping data in C# using HtmlAgilityPack. I come across HtmlAgilityPack because I need to get data from Zillow to analyze properties deals. I was able to scrape the data I want without much trouble using HtmlAgilityPack with a bit of XPath, LINQ and regular expression.
Continue readingIn this post, I summarize the different programming paradigms which I learned from reading the book “Clean Architecture A Craftsman’s guide to Software Structure and Design” by Robert Martin. The programming paradigms are structured programming, object oriented programming and functional programming.
Continue readingInheritance is something that comes up quite often during my programming experience. Whenever I have classes that share some logic or properties, I think of inheritance. However, sometimes, using inheritance ends up making a design more brittle. Through my experience and reading, I have learned a few things about inheritance.
Continue readingIn this post, I share the three libraries I find useful for unit testing a .NET project: EF Core SQLite Database Provider, Bogus and Moq.
Continue readingIn the previous post, I wrote about Barbara Liskov research paper on data abstraction and hierarchy. In the paper, the author states a property which exists between type and subtype. That property later becomes known as the Liskov Substitution Principle. In this post, I continue to go over the principle in more details and give examples. The principle is one out of the five software design principles in SOLID:
In this post, I talk about Delegate, Func, Action, anonymous methods and lambda expressions in C# and how they help with making the codes more concise and generic.
In this post, I share some bits I have learned about the async/await pattern. Specifically, I discuss some of the pitfalls of using async void in non event handlers.