Quote of the Day

more Quotes

Categories

Get notified of new posts

Buy me coffee

Why I Like Developing in C# and .NET

Published June 8, 2025 in .NET , C# , Software Development - 0 Comments

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.

Supporting Multiple Microsoft Teams Bots in One ASP.NET Core Application

Published December 26, 2024 in .NET , Architecture , ASP.NET core , C# , Software Development - 0 Comments

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.

Teams Bot1 Bot2 Bot3 Unified ASP.NET Core App LLM / Data Continue reading

What I learn from reading “Critical Chain”

Published November 3, 2024 in book review - 0 Comments

This book is about project management. Specifically, the book emphasizes the concepts of critical path vs critical chain. In a nutshell, a critical path accounts for all the tasks that must be done sequentially in order to complete a project. To estimate project completion using the critical path method, one needs to estimate all the times it takes for each step to complete and sum them up. For comparison, the critical chain accounts for the tasks as well as the resources to complete those tasks.

Continue reading

Building a fully multitenant system using Microsoft Identity Framework and SQL Row Level Security

Published November 3, 2024 in .NET , Database - 0 Comments

This post was written a while ago, but I’ve never published it.

In this post, I show an example of using SQL row level security to isolate data among different organizations in a same database using row level security. In that post, I show an example of reading a value in the database session context as part of the filtering logic. In this post, I show an example of how to preset a value in the database session context from ASP.NET core application, and also how to authenticate users from multiple tenants using Microsoft Identity Framework.

Continue reading

Should You Use Shared Models or Separate Sets of Models?

Published April 14, 2024 in Software Development - 0 Comments

In the past, I’ve worked on software projects where there were many model classes with very similar properties. I often questioned the necessity of having multiple models that appeared almost identical, as this approach seemed to introduce code duplication and unnecessary complexity. On the other hand, I’ve also contributed to projects where a few large, multipurpose model classes were used. These projects often suffered from inflexibility and the potential for widespread issues whenever modifications were required, as changes to one model could impact multiple components. In software development, the principle of ‘separation of concerns‘ is important for creating maintainable and scalable applications. Personally, I’ve come to favor using separate models to adhere to this principle. In this post, I’ll discuss the using shared versus separate models through a recent project example—a Blazor web application I worked on for building chatbots.

Continue reading

Understanding Message Flow in Microsoft Teams Bot Development.

Published April 1, 2024 in Software Development - 0 Comments

In this post, I share my knowledge and experience in developing a bot application for Teams using the Microsoft Teams Bot Framework. I will outline the key components involved in developing a bot application and explain how communication securely flows between an end user and the bot.

Continue reading

Reflections on ‘The Phoenix Project’: IT, Innovation, and the Path Forward”

Published February 26, 2024 in book review - 0 Comments


I recently finished reading “The Phoenix Project,” a novel that illustrates typical problems hindering the productivity and growth of organizations where software development plays a crucial role. I thoroughly enjoyed the book because it presents a compelling story with characters, a plot, protagonists, and antagonists, while also reinforcing important concepts to help IT professionals transform their organizations.

Continue reading

Coding by Day, Blogging by… Sometimes

Published February 18, 2024 in Personal Development - 0 Comments

It’s been a while since I last blogged. Reflecting on it, I can identify a few reasons for my absence from blogging. For instance, I have been dedicating my time to learning more about generative AI, as well as various libraries and frameworks for developing AI-powered applications. Another reason is that most topics I consider for my blog are readily available through official documentation and examples elsewhere. I don’t typically devise new patterns or methods. When I encounter an issue in my application that I can’t immediately resolve, I attempt to debug it by searching for the error message, or I consult the documentation. Often, I find a solution because someone else has already faced the same challenge. With the advent of tools like GitHub Copilot and ChatGPT, building applications has become significantly easier and more enjoyable. Consequently, instead of reading blog posts, one might simply consult an AI about an issue and potentially receive faster responses. This leads me to question whether I should invest my time in blogging rather than in learning or coding, and to evaluate the usefulness of my content. To be honest, the primary reason I haven’t been blogging is that it’s both difficult and time-consuming. It’s challenging because I need to determine my topic, organize my thoughts, and craft sentences that flow smoothly.

However, I believe that blogging regularly will enhance my writing and communication skills. Therefore, I am committed to maintaining this blog and will focus less on quality for the time being, prioritizing quantity instead. With AI assistance, my writing process should improve, as I can have it proofread my drafts. In fact, I asked ChatGPT to fix grammars for me, and it also made changes to a few wordings to make the sentences flow better, even though I only asked it to fix the grammar. I was also not sure what could be a title for this blog, and it came up with some suggestions that are a bit too dramatic, but after I asked it to come up with some light-hearted titles, I got one that I feel okay for this post. This gives me fewer excuses not to write. Moving forward, I aim to publish one blog post every week.

Displaying PDFs in Blazor.

Published November 24, 2023 in Web Development - 0 Comments

In this post, I go over a few options for displaying a PDF in a Blazor app, from the straightforward iframe src attribute embedding to ultilizing IJSRuntime for integration with JavaScript.


If you can access the PDF directly via an open GET endpoint, the easiest way is to embed the PDF in an iframe by setting the `src` attribute to the URL of the PDF, as shown in the snippet below generated by ChatGPT.

@page "/pdfviewer"

<h3>PDF Viewer</h3>

<iframe src="@PdfUrl" width="800" height="600" frameborder="0"></iframe>

@code {
    // Replace this URL with the actual URL of the PDF from your open REST API
    private string PdfUrl = "https://example.com/api/get-pdf";
}

However, if you need to include a bearer or security token in the header when calling the URL, the above method does not work since you don’t have a way to customize the header via src or other attributes of the iframe. One approach in this case is to programmatically construct the request to retrieve the PDF content as a byte array and set the data in the src attribute of the iframe, as shown in the below snippet generated by ChatGPT.

@page "/pdfviewer"

<h3>PDF Viewer</h3>

<iframe src="@PdfBase64" width="800" height="600" frameborder="0"></iframe>

@code {
    private string PdfBase64;

    protected override async Task OnInitializedAsync()
    {
        // Replace these values with your actual API endpoint and authentication logic
        var apiUrl = "https://example.com/api/get-pdf";
        var authToken = "YourAuthToken";

        // Call the helper method to retrieve the Base64-encoded PDF content
        PdfBase64 = await GetBase64Pdf(apiUrl, authToken);
    }

    private async Task<string> GetBase64Pdf(string apiUrl, string authToken)
    {
        using (var httpClient = new HttpClient())
        {
            // Set up headers, including authentication token if needed
            if (!string.IsNullOrEmpty(authToken))
            {
                httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
            }

            // Make the API request to get the PDF content
            var response = await httpClient.GetAsync(apiUrl);

            if (response.IsSuccessStatusCode)
            {
                // Convert the response content to Base64
                var pdfBytes = await response.Content.ReadAsByteArrayAsync();
                var base64String = Convert.ToBase64String(pdfBytes);
                return $"data:application/pdf;base64,{base64String}";
            }
            else
            {
                // Handle error scenarios based on your application needs
                // For simplicity, we return an empty string in case of an error
                return string.Empty;
            }
        }
    }
} 

The above solution works, but it copies the content of the PDF into memory and may cause performance issues for large PDFs. A more efficient solution is to stream the PDF content from the endpoint and use JavaScript to construct an object URL, which can be directly assigned to the src attribute of the iframe, as shown in the snippet below generated by ChatGPT.

@page "/pdfviewer"

<h3>PDF Viewer</h3>

<iframe id="pdfIframe" width="800" height="600" frameborder="0"></iframe>

@code {
    private string PdfContent;

   [Inject]
    private IJSRuntime JSRuntime { get; set; }

    protected override async Task OnInitializedAsync()
    {
        // Replace these values with your actual API endpoint and authentication logic
        var apiUrl = "https://example.com/api/get-pdf";
        var authToken = "YourAuthToken";

        // Call the helper method to retrieve the PDF content
        PdfContent = await GetPdfContent(apiUrl, authToken);
    }

    private async Task<string> GetPdfContent(string apiUrl, string authToken)
    {
        using (var httpClient = new HttpClient())
        {
            // Set up headers, including authentication token if needed
            if (!string.IsNullOrEmpty(authToken))
            {
                httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
            }

            // Make the API request to get the PDF content
            var response = await httpClient.GetAsync(apiUrl);

            if (response.IsSuccessStatusCode)
            {
                // Read the PDF content as a string
                return await response.Content.ReadAsStringAsync();
            }
            else
            {
                // Handle error scenarios based on your application needs
                // For simplicity, we return an empty string in case of an error
                return string.Empty;
            }
        }
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            // Use JavaScript to set the iframe src attribute with the PDF content
            await JSRuntime.InvokeVoidAsync("createPdfObjectUrl", PdfContent, "pdfIframe");
        }
    }
}
async function createPdfObjectUrl(stream, elementId) {
    const buffer = await stream.arrayBuffer();
    const blob = new Blob([buffer], { type: 'application/pdf' });
    const objectUrl = URL.createObjectURL(blob);
    const iframe = document.getElementById(elementId);
    iframe.src = objectUrl;

    // We don't need to keep the object url, let's release the memory
    URL.revokeObjectURL(objectUrl);
}

The above example loads the PDF content inside OnInitializedAsync() and calls the JavaScript function inside OnAfterRenderAsync(), using IJSRuntime. You can also load the PDF and call the JavaScript function on demand, for example, when the user clicks on a button.

The trick to the above solution is to realize that you need to explicitly load the script file before you can call the JavaScript function from the Blazor component. In my app, which is a Blazor server app, I load the JavaScript file in _host.cshtml, but using _layout.cshtml works fine as well. In a web assembly project, you probably need to use index.html. The code snippet below from ChatGPT shows an example of loading the script in _host.cshtml.

<!DOCTYPE html>
<html>
<head>
    <!-- Other head elements -->

    <!-- Reference your JavaScript file -->
    <script src="js/pdfviewer.js"></script>
</head>
<body>
    <!-- The rest of your HTML content -->

    <div id="app">
        <component type="typeof(App)" render-mode="ServerPrerendered" />
    </div>

    <!-- Other body elements -->
</body>
</html>

The above example assumes the js directory is under the wwwroot folder. Placing the JavaScript file under the wwwroot directory makes referencing the files simple since you can treat the wwwroot as the root directory of your web app. However, you may want to group all the related codes and files for the component together. In such a case, you can place the JavaScript file under the same path where you have the Blazor component. I prefer grouping the files together if the JavaScript codes are specific to the component.

Collocated JS files are publicly addressable using the path to the file in the project:

  • Pages, views, and components from a collocated scripts file in the app:{PATH}/{PAGE, VIEW, OR COMPONENT}.{EXTENSION}.js
    • The {PATH} placeholder is the path to the page, view, or component.
    • The {PAGE, VIEW, OR COMPONENT} placeholder is the page, view, or component.
    • The {EXTENSION} placeholder matches the extension of the page, view, or component, either razor or cshtml.
ASP.NET Core Blazor JavaScript interoperability (JS interop) | Microsoft Learn

If you run into issues with calling the JavaScript function, inspect the browser and ensure that the JavaScript file has been loaded under Sources, and check the console for errors.

Happy coding!

References

Call JavaScript functions from .NET methods in ASP.NET Core Blazor | Microsoft Learn

ASP.NET Core Blazor JavaScript interoperability (JS interop) | Microsoft Learn

1 2 3 11