by Joche Ojeda | Jan 13, 2025 | Uncategorized
As the new year (2025) starts, I want to share some insights from my role at Xari. While Javier and I founded the company together (he’s the Chief in Command, and I’ve dubbed myself the Minister of Dark Magic), our rapid growth has made these playful titles more meaningful than we expected.
Among my self-imposed responsibilities are:
- Providing ancient knowledge to the team (I’ve been coding since MS-DOS 6.1 – you do the math!)
- Testing emerging technologies
- Deciphering how and why our systems work
- Achieving the “impossible” (even if impractical, we love proving it can be done)
Our Technical Landscape
As a .NET shop, we develop everything from LOB applications to AI-powered object detection systems and mainframe database connectors. Our preference for C# isn’t just about the language – it’s about the power of the .NET ecosystem itself.
.NET’s architecture, with its intermediate language and JIT compilation, opens up fascinating possibilities for code manipulation. This brings us to one of my favorite features: Reflection, or more broadly, metaprogramming.
Enter Harmony: The Art of Runtime Magic
Harmony is a powerful library that transforms how we approach runtime method patching in .NET applications. Think of it as a sophisticated Swiss Army knife for metaprogramming. But why would you need it?
Real-World Applications
1. Performance Monitoring
[HarmonyPatch(typeof(CriticalService), "ProcessData")]
class PerformancePatch
{
static void Prefix(out Stopwatch __state)
{
__state = Stopwatch.StartNew();
}
static void Postfix(Stopwatch __state)
{
Console.WriteLine($"Processing took {__state.ElapsedMilliseconds}ms");
}
}
2. Feature Toggling in Legacy Systems
[HarmonyPatch(typeof(LegacySystem), "SaveToDatabase")]
class ModernizationPatch
{
static bool Prefix(object data)
{
if (FeatureFlags.UseNewStorage)
{
ModernDbContext.Save(data);
return false; // Skip old implementation
}
return true;
}
}
The Three Pillars of Harmony
Harmony offers three powerful ways to modify code:
1. Prefix Patches
- Execute before the original method
- Perfect for validation
- Can prevent original method execution
- Modify input parameters
2. Postfix Patches
- Run after the original method
- Ideal for logging
- Can modify return values
- Access to execution state
3. Transpilers
- Modify the IL code directly
- Most powerful but complex
- Direct instruction manipulation
- Used for advanced scenarios
Practical Example: Method Timing
Here’s a real-world example we use at Xari for performance monitoring:
[HarmonyPatch(typeof(Controller), "ProcessRequest")]
class MonitoringPatch
{
static void Prefix(out Stopwatch __state)
{
__state = Stopwatch.StartNew();
}
static void Postfix(MethodBase __originalMethod, Stopwatch __state)
{
__state.Stop();
Logger.Log($"{__originalMethod.Name} execution: {__state.ElapsedMilliseconds}ms");
}
}
When to Use Harmony
Harmony shines when you need to:
- Modify third-party code without source access
- Implement system-wide logging or monitoring
- Create modding frameworks
- Add features to sealed classes
- Test legacy systems
The Dark Side of Power
While Harmony is powerful, use it wisely:
- Avoid in production-critical systems where stability is paramount
- Consider simpler alternatives first
- Be cautious with high-performance scenarios
- Document your patches thoroughly
Conclusion
In our work at Xari, Harmony has proven invaluable for solving seemingly impossible problems. While it might seem like “dark magic,” it’s really about understanding and leveraging the powerful features of .NET’s architecture.
Remember: with great power comes great responsibility. Use Harmony when it makes sense, but always consider simpler alternatives first. Happy coding!
by Joche Ojeda | Jan 12, 2025 | ADO.NET, C#, CPU, dotnet, ORM, XAF, XPO
Introduction
In the .NET ecosystem, “AnyCPU” is often considered a silver bullet for cross-platform deployment. However, this assumption can lead to significant problems when your application depends on native assemblies. In this post, I want to share a personal story that highlights how I discovered these limitations and how native dependencies affect the true portability of AnyCPU applications, especially for database access through ADO.NET and popular ORMs.
My Journey to Understanding AnyCPU’s Limitations
Every year, around Thanksgiving or Christmas, I visit my friend, brother, and business partner Javier. Two years ago, during one of these visits, I made a decision that would lead me to a pivotal realization about AnyCPU architecture.
At the time, I was tired of traveling with my bulky MSI GE72 Apache Pro-24 gaming laptop. According to MSI’s official specifications, it weighed 5.95 pounds—but that number didn’t include the hefty charger, which brought the total to around 12 pounds. Later, I upgraded to an MSI GF63 Thin, which was lighter at 4.10 pounds—but with the charger, it was still around 7.5 pounds. Lugging these laptops through airports felt like a workout.
Determined to travel lighter, I purchased a MacBook Air with the M2 chip. At just 2.7 pounds, including the charger, the MacBook Air felt like a breath of fresh air. The Apple Silicon chip was incredibly fast, and I immediately fell in love with the machine.
Having used a MacBook Pro with Bootcamp and Windows 7 years ago, I thought I could recreate that experience by running a Windows virtual machine on my MacBook Air to check projects and do some light development while traveling.
The Virtualization Experiment
As someone who loves virtualization, I eagerly set up a Windows virtual machine on my MacBook Air. I grabbed my trusty Windows x64 ISO, set up the virtual machine, and attempted to boot it—but it failed. I quickly realized the issue was related to CPU architecture. My x64 ISO wasn’t compatible with the ARM-based M2 chip.
Undeterred, I downloaded a Windows 11 ISO for ARM architecture and created the VM. Success! Windows was up and running, and I installed Visual Studio along with my essential development tools, including DevExpress XPO (my favorite ORM).
The Demo Disaster
The real test came during a trip to Dubai, where I was scheduled to give a live demo showcasing how quickly you can develop Line-of-Business (LOB) apps with XAF. Everything started smoothly until I tried to connect my XAF app to the database. Despite my best efforts, the connection failed.
In the middle of the demo, I switched to an in-memory data provider to salvage the presentation. After the demo, I dug into the issue and realized the root cause was related to the CPU architecture. The native database drivers I was using weren’t compatible with the ARM architecture.
A Familiar Problem
This situation reminded me of the transition from x86 to x64 years ago. Back then, I encountered similar issues where native drivers wouldn’t load unless they matched the process architecture.
The Native Dependency Challenge
Platform-Specific Loading Requirements
Native DLLs must exactly match the CPU architecture of your application:
- If your app runs as x86, it can only load x86 native DLLs.
- If running as x64, it requires x64 native DLLs.
- ARM requires ARM-specific binaries.
- ARM64 requires ARM64-specific binaries.
There is no flexibility—attempting to load a DLL compiled for a different architecture results in an immediate failure.
How Native Libraries are Loaded
When your application loads a native DLL, the operating system follows a specific search pattern:
- The application’s directory
- System directories (System32/SysWOW64)
- Directories listed in the PATH environment variable
Crucially, these native libraries must match the exact architecture of the running process.
// This seemingly simple code
[DllImport("native.dll")]
static extern void NativeMethod();
// Actually requires:
// - native.dll compiled for x86 when running as 32-bit
// - native.dll compiled for x64 when running as 64-bit
// - native.dll compiled for ARM64 when running on ARM64
The SQL Server Example
Let’s look at SQL Server connectivity, a common scenario where the AnyCPU illusion breaks down:
// Traditional ADO.NET connection
using (var connection = new SqlConnection(connectionString))
{
// This requires SQL Native Client
// Which must match the process architecture
await connection.OpenAsync();
}
Even though your application is compiled as AnyCPU, the SQL Native Client must match the process architecture. This becomes particularly problematic on newer architectures like ARM64, where native drivers may not be available.
Impact on ORMs
Entity Framework Core
Entity Framework Core, despite its modern design, still relies on database providers that may have native dependencies:
public class MyDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// This configuration depends on:
// 1. SQL Native Client
// 2. Microsoft.Data.SqlClient native components
optionsBuilder.UseSqlServer(connectionString);
}
}
DevExpress XPO
DevExpress XPO faces similar challenges:
// XPO configuration
string connectionString = MSSqlConnectionProvider.GetConnectionString("server", "database");
XpoDefault.DataLayer = XpoDefault.GetDataLayer(connectionString, AutoCreateOption.DatabaseAndSchema);
// The MSSqlConnectionProvider relies on the same native SQL Server components
Solutions and Best Practices
1. Architecture-Specific Deployment
Instead of relying on AnyCPU, consider creating architecture-specific builds:
<PropertyGroup>
<Platforms>x86;x64;arm64</Platforms>
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
</PropertyGroup>
2. Runtime Provider Selection
Implement smart provider selection based on the current architecture:
public static class DatabaseProviderFactory
{
public static IDbConnection GetProvider()
{
return RuntimeInformation.ProcessArchitecture switch
{
Architecture.X86 => new SqlConnection(), // x86 native provider
Architecture.X64 => new SqlConnection(), // x64 native provider
Architecture.Arm64 => new Microsoft.Data.SqlClient.SqlConnection(), // ARM64 support
_ => throw new PlatformNotSupportedException()
};
}
}
3. Managed Fallbacks
Implement fallback strategies when native providers aren’t available:
public class DatabaseConnection
{
public async Task<IDbConnection> CreateConnectionAsync()
{
try
{
var connection = new SqlConnection(_connectionString);
await connection.OpenAsync();
return connection;
}
catch (DllNotFoundException)
{
var managedConnection = new Microsoft.Data.SqlClient.SqlConnection(_connectionString);
await managedConnection.OpenAsync();
return managedConnection;
}
}
}
4. Deployment Considerations
- Include all necessary native dependencies for each target architecture.
- Use architecture-specific directories in your deployment.
- Consider self-contained deployment to include the correct runtime.
Real-World Implications
This experience taught me that while AnyCPU provides excellent flexibility for managed code, it has limitations when dealing with native dependencies. These limitations become more apparent in scenarios like cloud deployments, ARM64 devices, and live demos.
Conclusion
The transition to ARM architecture is accelerating, and understanding the nuances of AnyCPU and native dependencies is more important than ever. By planning for architecture-specific deployments and implementing fallback strategies, you can build more resilient applications that can thrive in a multi-architecture world.
by Joche Ojeda | Jan 9, 2025 | dotnet
While researching useful features in .NET 9 that could benefit XAF/XPO developers, I discovered something particularly interesting: Version 7 GUIDs (RFC 9562 specification). These new GUIDs offer a crucial feature – they’re sortable.
This discovery brought me back to an issue I encountered two years ago while working on the SyncFramework. We faced a peculiar problem where Deltas were correctly generated but processed in the wrong order in production environments. The occurrences seemed random, and no clear pattern emerged. Initially, I thought using Delta primary keys (GUIDs) to sort the Deltas would ensure they were processed in their generation order. However, this assumption proved incorrect. Through testing, I discovered that GUID generation couldn’t be trusted to be sequential. This issue affected multiple components of the SyncFramework. Whether generating GUIDs in C# or at the database level, there was no guarantee of sequential ordering. Different database engines could sort GUIDs differently. To address this, I implemented a sequence service as a solution.Enter .NET 9 with its Version 7 GUIDs (conforming to RFC 9562 specification). These new GUIDs are genuinely sequential, making them reliable for sorting operations.
To demonstrate this improvement, I created a test solution for XAF with a custom base object. The key implementation occurs in the OnSaving method:
protected override void OnSaving()
{
base.OnSaving();
if (!(Session is NestedUnitOfWork) && Session.IsNewObject(this) && oid.Equals(Guid.Empty))
{
oid = Guid.CreateVersion7();
}
}
Notice the use of CreateVersion7()
instead of the traditional NewGuid()
. For comparison, I also created another domain object using the traditional GUID generation:
protected override void OnSaving()
{
base.OnSaving();
if (!(Session is NestedUnitOfWork) && Session.IsNewObject(this) && oid.Equals(Guid.Empty))
{
oid = Guid.NewGuid();
}
}
When creating multiple instances of the traditional GUID domain object, you’ll notice that the greater the time interval between instance creation, the less likely the GUIDs will maintain sequential ordering.
GUID Version 7

GUID Old Version

This new feature in .NET 9 could significantly simplify scenarios where sequential ordering is crucial, eliminating the need for additional sequence services in many cases. Here is the repo on GitHubHappy coding until next time!
Related article
On my GUID, common problems using GUID identifiers | Joche Ojeda
by Joche Ojeda | Jan 2, 2025 | XtraReports
Introduction ?
If you’re familiar with Windows Forms development, transitioning to XtraReports will feel remarkably natural. This guide explores how XtraReports leverages familiar Windows Forms concepts while extending them for robust reporting capabilities.
? Quick Tip: Think of XtraReports as Windows Forms optimized for paper output instead of screen output!
A Personal Journey ✨
Microsoft released .NET Framework in late 2002. At the time, I was a VB6 developer, relying on Crystal Reports 7 for reporting. By 2003, my team was debating whether to transition to this new thing called .NET. We were concerned about VB6’s longevity—thinking it had just a couple more years left. How wrong we were! Even today, VB6 applications are still running in some places (it’s January 2, 2025, as I write this).
Back in the VB6 era, we used the Crystal Reports COM object to integrate reports. When we finally moved to .NET Framework, we performed some “black magic” to continue using our existing 700 reports across nine countries. The decision to fully embrace .NET was repeatedly delayed due to the sheer volume of reports we had to manage. Our ultimate goal was to unify our reporting and parameter forms within a single development environment.
This led us to explore other technologies. While considering Delphi, we discovered DevExpress. My boss procured our first DevExpress .NET license for Windows Forms, marking the start of my adventure with DevExpress and XtraReports. Initially, transitioning from the standalone Crystal Report Designer to the IDE-based XtraReports Designer was challenging. To better understand how XtraReports worked, I decided to write reports programmatically instead of using the visual designer.
Architectural Similarities ?️
XtraReports mirrors many fundamental Windows Forms concepts:
Source |
Destination |
XtraReport Class |
Report Designer Surface |
XtraReport Class |
Control Container |
XtraReport Class |
Event System |
XtraReport Class |
Properties Window |
Control Container |
Labels & Text |
Control Container |
Tables & Grids |
Control Container |
Images & Charts |
Report Designer Surface |
Control Toolbox |
Report Designer Surface |
Design Surface |
Report Designer Surface |
Preview Window |
Like how Windows Forms applications start with a Form
class, XtraReports begin with an XtraReport
base class. Both serve as containers that can:
- Host other controls
- Manage layout
- Handle events
- Support data binding
Visual Designer Experience ?
The design experience remains consistent with Windows Forms:
Windows Forms |
XtraReports |
Form Designer |
Report Designer |
Toolbox |
Report Controls |
Properties Window |
Properties Grid |
Component Tray |
Component Tool |
Control Ecosystem ?
XtraReports provides analogous controls to Windows Forms:
// Windows Forms
public partial class CustomerForm : Form
{
private Label customerNameLabel;
private DataGridView orderDetailsGrid;
}
// XtraReports
public partial class CustomerReport : XtraReport
{
private XRLabel customerNameLabel;
private XRTable orderDetailsTable;
}
Common control mappings:
- Label ➡️ XRLabel
- Panel ➡️ XRPanel
- PictureBox ➡️ XRPictureBox
- DataGridView ➡️ XRTable
- GroupBox ➡️ Band
- UserControl ➡️ Subreport
Data Binding Patterns ?
The data binding syntax maintains familiarity:
// Windows Forms data binding
customerNameLabel.DataBindings.Add("Text", customerDataSet, "Customers.Name");
// XtraReports data binding
customerNameLabel.ExpressionBindings.Add(
new ExpressionBinding("Text", "[Name]"));
Code Architecture ?️
The code-behind model remains consistent:
public partial class CustomerReport : DevExpress.XtraReports.UI.XtraReport
{
public CustomerReport()
{
InitializeComponent(); // Familiar Windows Forms pattern
}
private void CustomerReport_BeforePrint(object sender, PrintEventArgs e)
{
// Event handling similar to Windows Forms
// Instead of Form_Load, we have Report_BeforePrint
}
}
Key Differences ⚡
While similarities abound, important differences exist:
- Output Focus ?️
- Windows Forms: Screen-based interaction
- XtraReports: Print/export optimization
- Layout Model ?
- Windows Forms: Flexible screen layouts
- XtraReports: Page-based layouts with bands
- Control Behavior ?
- Windows Forms: Interactive controls
- XtraReports: Display-oriented controls
- Data Processing ?️
- Windows Forms: Real-time data interaction
- XtraReports: Batch data processing
Some Advices ?
- Design Philosophy
// Think in terms of paper output
public class InvoiceReport : XtraReport
{
protected override void OnBeforePrint(PrintEventArgs e)
{
// Calculate page breaks
// Optimize for printing
}
}
- Layout Strategy
- Use bands for logical grouping
- Consider paper size constraints
- Plan for different export formats
- Data Handling
- Pre-process data when possible
- Use calculated fields for complex logic
- Consider subreports for complex layouts
by Joche Ojeda | Dec 2, 2024 | Blazor
Over time, I transitioned to using the first versions of my beloved framework, XAF. As you might know, XAF generates a polished and functional UI out of the box. Using XAF made me more of a backend developer since most of the development work wasn’t visual—especially in the early versions, where the model designer was rudimentary (it’s much better now).
Eventually, I moved on to developing .NET libraries and NuGet packages, diving deep into SOLID design principles. Fun fact: I actually learned about SOLID from DevExpress TV. Yes, there was a time before YouTube when DevExpress posted videos on technical tasks!
Nowadays, I feel confident creating and publishing my own libraries as NuGet packages. However, my “old monster” was still lurking in the shadows: UI components. I finally decided it was time to conquer it, but first, I needed to choose a platform. Here were my options:
- Windows Forms: A robust and mature platform but limited to desktop applications.
- WPF: A great option with some excellent UI frameworks that I love, but it still feels a bit “Windows Forms-ish” to me.
- Xamarin/Maui: I’m a big fan of Xamarin Forms and Xamarin/Maui XAML, but they’re primarily focused on device-specific applications.
- Blazor: This was the clear winner because it allows me to create desktop applications using Electron, embed components into Windows Forms, or even integrate with MAUI.
Recently, I’ve been helping my brother with a project in Blazor. (He’s not a programmer, but I am.) This gave me an opportunity to experiment with design patterns to get the most out of my components, which started as plain HTML5 pages.
Without further ado, here are the key insights I’ve gained so far.
Building high-quality Blazor components requires attention to both the C# implementation and Razor markup patterns. This guide combines architectural best practices with practical implementation patterns to create robust, reusable components.
1. Component Architecture and Organization
Parameter Organization
Start by organizing parameters into logical groups for better maintainability:
public class CustomForm : ComponentBase
{
// Layout Parameters
[Parameter] public string Width { get; set; }
[Parameter] public string Margin { get; set; }
[Parameter] public string Padding { get; set; }
// Validation Parameters
[Parameter] public bool EnableValidation { get; set; }
[Parameter] public string ValidationMessage { get; set; }
// Event Callbacks
[Parameter] public EventCallback<bool> OnValidationComplete { get; set; }
[Parameter] public EventCallback<string> OnSubmit { get; set; }
}
Corresponding Razor Template
<div class="form-container" style="width: @Width; margin: @Margin; padding: @Padding">
<form @onsubmit="HandleSubmit">
@if (EnableValidation)
{
<div class="validation-message">
@ValidationMessage
</div>
}
@ChildContent
</form>
</div>
2. Smart Default Values and Template Composition
Component Implementation
public class DataTable<T> : ComponentBase
{
[Parameter] public int PageSize { get; set; } = 10;
[Parameter] public bool ShowPagination { get; set; } = true;
[Parameter] public string EmptyMessage { get; set; } = "No data available";
[Parameter] public IEnumerable<T> Items { get; set; } = Array.Empty<T>();
[Parameter] public RenderFragment HeaderTemplate { get; set; }
[Parameter] public RenderFragment<T> RowTemplate { get; set; }
[Parameter] public RenderFragment FooterTemplate { get; set; }
}
Razor Implementation
<div class="table-container">
@if (HeaderTemplate != null)
{
<header class="table-header">
@HeaderTemplate
</header>
}
<div class="table-content">
@if (!Items.Any())
{
<div class="empty-state">@EmptyMessage</div>
}
else
{
@foreach (var item in Items)
{
@RowTemplate(item)
}
}
</div>
@if (ShowPagination)
{
<div class="pagination">
<!-- Pagination implementation -->
</div>
}
</div>
3. Accessibility and Unique IDs
Component Implementation
public class FormField : ComponentBase
{
private string fieldId = $"field-{Guid.NewGuid():N}";
private string labelId = $"label-{Guid.NewGuid():N}";
private string errorId = $"error-{Guid.NewGuid():N}";
[Parameter] public string Label { get; set; }
[Parameter] public string Error { get; set; }
[Parameter] public bool Required { get; set; }
}
Razor Implementation
<div class="form-field">
<label id="@labelId" for="@fieldId">
@Label
@if (Required)
{
<span class="required" aria-label="required">*</span>
}
</label>
<input id="@fieldId"
aria-labelledby="@labelId"
aria-describedby="@errorId"
aria-required="@Required" />
@if (!string.IsNullOrEmpty(Error))
{
<div id="@errorId" class="error-message" role="alert">
@Error
</div>
}
</div>
4. Virtualization and Performance
Component Implementation
public class VirtualizedList<T> : ComponentBase
{
[Parameter] public IEnumerable<T> Items { get; set; }
[Parameter] public RenderFragment<T> ItemTemplate { get; set; }
[Parameter] public int ItemHeight { get; set; } = 50;
[Parameter] public Func<ItemsProviderRequest, ValueTask<ItemsProviderResult<T>>> ItemsProvider { get; set; }
}
Razor Implementation
<div class="virtualized-container" style="height: 500px; overflow-y: auto;">
<Virtualize Items="@Items"
ItemSize="@ItemHeight"
ItemsProvider="@ItemsProvider"
Context="item">
<ItemContent>
<div class="list-item" style="height: @(ItemHeight)px">
@ItemTemplate(item)
</div>
</ItemContent>
<Placeholder>
<div class="loading-placeholder" style="height: @(ItemHeight)px">
<div class="loading-animation"></div>
</div>
</Placeholder>
</Virtualize>
</div>
Best Practices Summary
1. Parameter Organization
- Group related parameters with clear comments
- Provide meaningful default values
- Use parameter validation where appropriate
2. Template Composition
- Use RenderFragment for customizable sections
- Provide default templates when needed
- Enable granular control over component appearance
3. Accessibility
- Generate unique IDs for form elements
- Include proper ARIA attributes
- Support keyboard navigation
4. Performance
- Implement virtualization for large datasets
- Use loading states and placeholders
- Optimize rendering with appropriate conditions
Conclusion
Building effective Blazor components requires attention to both the C# implementation and Razor markup. By following these patterns and practices, you can create components that are:
- Highly reusable
- Performant
- Accessible
- Easy to maintain
- Flexible for different use cases
Remember to adapt these practices to your specific needs while maintaining clean component design principles.
by Joche Ojeda | Dec 1, 2024 | Blazor
My journey with Microsoft Semantic Kernel marked the beginning of a new adventure: stepping out of my comfort zone as a backend developer to create applications with user interfaces, rather than just building apps for unit and integration testing.
I naturally chose Blazor as my UI framework, and I’ll be sharing my frontend development experiences here. Sometimes it can be frustratingly difficult to accomplish seemingly simple tasks (like centering a div!), but AI assistants like GitHub Copilot have been incredibly helpful in reducing those pain points.
One of my recent challenges involved programmatically including JavaScript and CSS in Blazor applications. I prefer an automated approach rather than manually adding tags to HTML. Back in the .NET 5 era, I wrote an article about using tag helpers for this purpose, which you can find here
However, I recently discovered that my original approach no longer works. I’ve been developing several prototypes using the new DevExpress Chat component, and many of these prototypes include custom components that require JavaScript and CSS. Despite my attempts, I couldn’t get these components to work with the tag helpers, and the reason wasn’t immediately obvious. During the Thanksgiving break, I decided to investigate this issue, and I’d like to share what I found.
With the release of .NET 8, Blazor introduced a new web app template that unifies Blazor Server and WebAssembly into a single project structure. This change affects how we inject content into the document’s head section, particularly when working with Tag Helpers or components.
Understanding the Changes
In previous versions of Blazor, we typically worked with _Host.cshtml
for server-side rendering, where traditional ASP.NET Core Tag Helpers could target the <head>
element directly. The new .NET 8 Blazor Web App template uses App.razor
as the root component and introduces the <HeadOutlet>
component for managing head content.
Approach 1: Adapting Tag Helpers
If you’re migrating existing Tag Helpers or creating new ones for head content injection, you’ll need to modify them to target HeadOutlet instead of the head element:
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace YourNamespace
{
[HtmlTargetElement("HeadOutlet")]
public class CustomScriptTagHelper : TagHelper
{
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.PostContent.AppendHtml(
"<script src=\"_content/YourLibrary/js/script.js\"></script>"
);
}
}
}
Remember to register your Tag Helper in _Imports.razor
:
@addTagHelper *, YourLibrary
Approach 2: Using Blazor Components (Recommended)
While adapting Tag Helpers works, Blazor offers a more idiomatic approach using components and the HeadContent component. This approach aligns better with Blazor’s component-based architecture:
@namespace YourNamespace
@implements IComponentRenderMode
<HeadContent>
<script src="_content/YourLibrary/js/script.js"></script>
</HeadContent>
To use this component in your App.razor
:
<head>
<!-- Other head elements -->
<HeadOutlet @rendermode="RenderModeForPage" />
<YourScriptComponent @rendermode="RenderModeForPage" />
</head>
Benefits of the Component Approach
- Better Integration: Components work seamlessly with Blazor’s rendering model
- Render Mode Support: Easy to control rendering based on the current render mode (Interactive Server, WebAssembly, or Auto)
- Dynamic Content: Can leverage Blazor’s full component lifecycle and state management
- Type Safety: Provides compile-time checking and better tooling support
Best Practices
- Prefer the component-based approach for new development
- Use Tag Helpers only when migrating existing code or when you need specific ASP.NET Core pipeline integration
- Always specify the
@rendermode
attribute to ensure proper rendering in different scenarios
- Place custom head content components after HeadOutlet to ensure proper ordering
Conclusion
While both approaches work in .NET 8 Blazor Web Apps, the component-based approach using HeadContent provides a more natural fit with Blazor’s architecture and offers better maintainability and flexibility. When building new applications, consider using components unless you have a specific need for Tag Helper functionality.
by Joche Ojeda | Nov 2, 2024 | A.I, Semantic Kernel
Today, when I woke up, it was sunny but really cold, and the weather forecast said that snow was expected.
So, I decided to order ramen and do a “Saturday at home” type of project. My tools of choice for this experiment are:
1) DevExpress Chat Component for Blazor
I’m thrilled they have this component. I once wrote my own chat component, and it’s a challenging task, especially given the variety of use cases.
2) Semantic Kernel
I’ve been experimenting with Semantic Kernel for a while now, and let me tell you—it’s a fantastic tool if you’re in the .NET ecosystem. It’s so cool to have native C# code to interact with AI services in a flexible way, making your code mostly agnostic to the AI provider—like a WCF for AIs.
Goal of the Experiment
The goal for today’s experiment is to render a list of products as a carousel within a chat conversation.
Configuration
To accomplish this, I’ll use prompt execution settings in Semantic Kernel to ensure that the response from the LLM is always in JSON format as a string.
var Settings = new OpenAIPromptExecutionSettings
{
MaxTokens = 500,
Temperature = 0.5,
ResponseFormat = "json_object"
};
The key part here is the response format. The chat completion can respond in two ways:
- Text: A simple text answer.
- JSON Object: This format always returns a JSON object, with the structure provided as part of the prompt.
With this approach, we can deserialize the LLM’s response to an object that helps conditionally render the message content within the DevExpress Chat Component.
Structure
Here’s the structure I’m using:
public class MessageData
{
public string Message { get; set; }
public List Options { get; set; }
public string MessageTemplateName { get; set; }
}
public class OptionSet
{
public string Name { get; set; }
public string Description { get; set; }
public List Options { get; set; }
}
public class Option
{
public string Image { get; set; }
public string Url { get; set; }
public string Description { get; set; }
};
- MessageData: This structure will always be returned by our LLM.
- Option: A list of options for a message, which also serves as data for possible responses.
- OptionSet: A list of possible responses to feed into the prompt execution settings.
Prompt Execution Settings
One more step on the Semantic Kernel side is configuring the prompt execution settings:
var Settings = new OpenAIPromptExecutionSettings
{
MaxTokens = 500,
Temperature = 0.5,
ResponseFormat = "json_object"
};
Settings.ChatSystemPrompt = $"You need to answer using this JSON format with this structure {Structure} " +
$"Before giving an answer, check if it exists within this list of option sets {OptionSets}. " +
$"If your answer does not include options, the message template value should be 'Message'; otherwise, it should be 'Options'.";
In the prompt, we specify the structure {Structure}
we want as a response, provide a list of possible options for the message in the {OptionSets}
variable, and add a final line to guide the LLM on which template type to use.
Example Requests and Responses
For example, when executing the following request:
- Prompt: “Show me a list of Halloween costumes for cats.”
We’ll get this response from the LLM:
{
"Message": "Please select one of the Halloween costumes for cats",
"Options": [
{"Image": "./images/catblack.png", "Url": "https://cat.com/black", "Description": "Black cat costume"},
{"Image": "./images/catwhite.png", "Url": "https://cat.com/white", "Description": "White cat costume"},
{"Image": "./images/catorange.png", "Url": "https://cat.com/orange", "Description": "Orange cat costume"}
],
"MessageTemplateName": "Options"
}
With this JSON structure, we can conditionally render messages in the chat component as follows:
<DxAIChat CssClass="my-chat" MessageSent="MessageSent">
<MessageTemplate>
<div>
@{
if (@context.Typing)
{
<span>Loading...</span>
}
else
{
MessageData md = null;
try
{
md = JsonSerializer.Deserialize<MessageData>(context.Content);
}
catch
{
md = null;
}
if (md == null)
{
<div class="my-chat-content">
@context.Content
</div>
}
else
{
if (md.MessageTemplateName == "Options")
{
<div class="centered-carousel">
<Carousel class="carousel-container" Width="280" IsFade="true">
@foreach (var option in md.Options)
{
<CarouselItem>
<ChildContent>
<div>
<img src="@option.Image" alt="demo-image" />
<Button Color="Color.Primary" class="carousel-button">@option.Description</Button>
</div>
</ChildContent>
</CarouselItem>
}
</Carousel>
</div>
}
else if (md.MessageTemplateName == "Message")
{
<div class="my-chat-content">
@md.Message
</div>
}
}
}
}
</div>
</MessageTemplate>
</DxAIChat>
End Solution Example
Here’s an example of the final solution:

You can find the full source code here: https://github.com/egarim/devexpress-ai-chat-samples and a short video here https://youtu.be/dxMnOWbe3KA
by Joche Ojeda | Oct 30, 2024 | Uncategorized
Lately, I’ve been working extensively on interacting with LLMs using the Semantic Kernel framework. My experiments usually start as NUnit test projects, where I prototype my ideas.
Once an experiment is successful, I move it to XAF. Recently, I faced challenges with executing asynchronous code and updating the XAF UI. This process is tricky because some solutions might appear to work but fail under certain conditions.
Goals
Here’s what I aim to achieve:
- Execute asynchronous code within the execute handler of an action.
- Notify the UI and access the current view, object, and object space.
- Run multiple operations on a background thread.
For more background, check out these links on async executions within XAF actions:
WebForms
Blazor
Complete source code for this test can be found on GitHub.
Common Cases
1. Blocking the UI Thread (this will not work)
If you don’t make your action async, attempting to get the awaiter will block the UI thread, freezing your application.
ActionBlockUIThread = new SimpleAction(this, nameof(ActionBlockUIThread), "View");
ActionBlockUIThread.Execute += ActionBlockUIThread_Execute;
protected virtual void ActionBlockUIThread_Execute(object sender, SimpleActionExecuteEventArgs e) {
var Tasks = GetTaskList();
StringBuilder Results = new StringBuilder();
foreach (var item in Tasks) {
Results.AppendLine(item.Invoke().GetAwaiter().GetResult().ToString());
}
MessageOptions options = new MessageOptions { Duration = 2000, Message = Results.ToString(), Type = InformationType.Success };
Application.ShowViewStrategy.ShowMessage(options);
}
2. Using Async Modifier (somehow works)
Marking your handler as async prevents blocking but keeps the UI responsive, which can allow the user to modify or navigate away from the current view, causing exceptions.
protected virtual async void ActionWithAsyncModifier_Execute(object sender, SimpleActionExecuteEventArgs e) {
var Tasks = GetTaskList();
StringBuilder Results = new StringBuilder();
foreach (var item in Tasks) {
var CurrentResult = await item.Invoke();
Results.AppendLine(CurrentResult.ToString());
}
MessageOptions options = new MessageOptions { Duration = 2000, Message = Results.ToString(), Type = InformationType.Success };
Application.ShowViewStrategy.ShowMessage(options);
}
A slightly modified Blazor version of this code also illustrates similar issues.
Executing Object Space Operations Inside Async Action, things that can happen
This approach still leaves the UI responsive, risking disposal of object space if the user navigates away, if that happens you will end up with an exception
protected virtual async void ActionWithAsyncModifierAndOsOperations_Execute(object sender, SimpleActionExecuteEventArgs e) {
var Instance = GetInstance();
var Tasks = GetTaskList();
StringBuilder Results = new StringBuilder();
foreach (var item in Tasks) {
var CurrentResult = await item.Invoke();
Results.AppendLine(CurrentResult.ToString());
}
Instance.Result = Results.ToString();
ViewCommit();
MessageOptions options = new MessageOptions { Duration = 3000, Message = Instance.Result, Type = InformationType.Success };
Application.ShowViewStrategy.ShowMessage(options);
}
Proposed Solution
My solution utilizes a background worker to handle async operations while locking the UI thread with a loading indicator. This allows us to react to progress on the UI thread.
Async Background Worker Example
Here’s how the AsyncBackgroundWorker is set up and used:
protected virtual void AsyncActionWithAsyncBackgroundWorker_Execute(object sender, SimpleActionExecuteEventArgs e) {
var tasks = GetTaskList();
var worker = new AsyncBackgroundWorker
Handling Background Worker Events
protected virtual void ProcessingDone(Dictionary<int, object> results) {
// Interact with UI and object space
}
protected virtual void OnReportProgress(int progress, string status, object result) {
MessageOptions options = new MessageOptions { Duration = 2000, Message = status, Type = InformationType.Success };
Application.ShowViewStrategy.ShowMessage(options);
}
Blazor Implementation
The Blazor version manages UI locking by showing a loading indicator and reporting progress through the UI thread:
source here: https://github.com/egarim/XafAsyncActions/blob/master/XafAsyncActions.Blazor.Server/Controllers/MyViewControllerBlazor.cs
protected async override void AsyncActionWithAsyncBackgroundWorker_Execute(object sender, SimpleActionExecuteEventArgs e) {
loading.Hold("Loading");
base.AsyncActionWithAsyncBackgroundWorker_Execute(sender, e);
}
protected async override void ProcessingDone(Dictionary<int, object> results) {
base.ProcessingDone(results);
loading.Release("Loading");
}
When you run this implementation, it will look like this

As you can see the task are being run on the background worker and every time a task is finish is reported back to the U.I thread where we can execute a notification (this is actually optional)
When all the tasks are finished, I hide the loading indicator, and the user can interact with the view again
I hope this article clarifies async execution in XAF. I will update the repository as new scenarios arise.
by Joche Ojeda | Oct 21, 2024 | A.I, Semantic Kernel
A few weeks ago, I received the exciting news that DevExpress had released a new chat component (you can read more about it here). This was a big deal for me because I had been experimenting with the Semantic Kernel for almost a year. Most of my experiments fell into three categories:
- NUnit projects with no UI (useful when you need to prove a concept).
- XAF ASP.NET projects using a large textbox (String with unlimited size in XAF) to emulate a chat control.
- XAF applications using a custom chat component that I developed—which, honestly, didn’t look great because I’m more of a backend developer than a UI specialist. Still, the component did the job.
Once I got my hands on the new Chat component, the first thing I did was write a property editor to easily integrate it into XAF. You can read more about property editors in XAF here.
With the Chat component property editor in place, I had the necessary tool to accelerate my experiments with the Semantic Kernel (learn more about the Semantic Kernel here).
The Current Experiment
A few weeks ago, I wrote an implementation of the Semantic Kernel Memory Store using DevExpress’s XPO as the data storage solution. You can read about that implementation here. The next step was to integrate this Semantic Memory Store into XAF, and that’s now done. Details about that process can be found here.
What We Have So Far
- A Chat component property editor for XAF.
- A Semantic Kernel Memory Store for XPO that’s compatible with XAF.
With these two pieces, we can create an interesting prototype. The goals for this experiment are:
- Saving “memories” into a domain object (via XPO).
- Querying these memories through the Chat component property editor, using Semantic Kernel chat completions (compatible with all OpenAI APIs).
Step 1: Memory Collection Object
The first thing we need is an object that represents a collection of memories. Here’s the implementation:
[DefaultClassOptions]
public class MemoryChat : BaseObject
{
public MemoryChat(Session session) : base(session) {}
public override void AfterConstruction()
{
base.AfterConstruction();
this.MinimumRelevanceScore = 0.20;
}
double minimumRelevanceScore;
string name;
[Size(SizeAttribute.DefaultStringMappingFieldSize)]
public string Name
{
get => name;
set => SetPropertyValue(nameof(Name), ref name, value);
}
public double MinimumRelevanceScore
{
get => minimumRelevanceScore;
set => SetPropertyValue(nameof(MinimumRelevanceScore), ref minimumRelevanceScore, value);
}
[Association("MemoryChat-MemoryEntries")]
public XPCollection<MemoryEntry> MemoryEntries
{
get => GetCollection<MemoryEntry>(nameof(MemoryEntries));
}
}
This is a simple object. The two main properties are the MinimumRelevanceScore
, which is used for similarity searches with embeddings, and the collection of MemoryEntries
, where different memories are stored.
Step 2: Adding Memories
The next task is to easily append memories to that collection. I decided to use a non-persistent object displayed in a popup view with a large text area. When the user confirms the action in the dialog, the text gets vectorized and stored as a memory in the collection. You can see the implementation of the view controller here.
Let me highlight the important parts.
When we create the view for the popup window:
private void AppendMemory_CustomizePopupWindowParams(object sender, CustomizePopupWindowParamsEventArgs e)
{
var os = this.Application.CreateObjectSpace(typeof(TextMemory));
var textMemory = os.CreateObject<TextMemory>();
e.View = this.Application.CreateDetailView(os, textMemory);
}
The goal is to show a large textbox where the user can type any text. When they confirm, the text is vectorized and stored as a memory.
Next, storing the memory:
private async void AppendMemory_Execute(object sender, PopupWindowShowActionExecuteEventArgs e)
{
var textMemory = e.PopupWindowViewSelectedObjects[0] as TextMemory;
var currentMemoryChat = e.SelectedObjects[0] as MemoryChat;
var store = XpoMemoryStore.ConnectAsync(xafEntryManager).GetAwaiter().GetResult();
var semanticTextMemory = GetSemanticTextMemory(store);
await semanticTextMemory.SaveInformationAsync(currentMemoryChat.Name, id: Guid.NewGuid().ToString(), text: textMemory.Content);
}
Here, the GetSemanticTextMemory
method plays a key role:
private static SemanticTextMemory GetSemanticTextMemory(XpoMemoryStore store)
{
var embeddingModelId = "text-embedding-3-small";
var getKey = () => Environment.GetEnvironmentVariable("OpenAiTestKey", EnvironmentVariableTarget.Machine);
var kernel = Kernel.CreateBuilder()
.AddOpenAIChatCompletion(ChatModelId, getKey.Invoke())
.AddOpenAITextEmbeddingGeneration(embeddingModelId, getKey.Invoke())
.Build();
var embeddingGenerator = new OpenAITextEmbeddingGenerationService(embeddingModelId, getKey.Invoke());
return new SemanticTextMemory(store, embeddingGenerator);
}
This method sets up an embedding generator used to create semantic memories.
Step 3: Querying Memories
To query the stored memories, I created a non-persistent type that interacts with the chat component:
public interface IMemoryData
{
IChatCompletionService ChatCompletionService { get; set; }
SemanticTextMemory SemanticTextMemory { get; set; }
string CollectionName { get; set; }
string Prompt { get; set; }
double MinimumRelevanceScore { get; set; }
}
This interface provides the necessary services to interact with the chat component, including ChatCompletionService
and SemanticTextMemory
.
Step 4: Handling Messages
Lastly, we handle message-sent callbacks, as explained in this article:
async Task MessageSent(MessageSentEventArgs args)
{
ChatHistory.AddUserMessage(args.Content);
var answers = Value.SemanticTextMemory.SearchAsync(
collection: Value.CollectionName,
query: args.Content,
limit: 1,
minRelevanceScore: Value.MinimumRelevanceScore,
withEmbeddings: true
);
string answerValue = "No answer";
await foreach (var answer in answers)
{
answerValue = answer.Metadata.Text;
}
string messageContent = answerValue == "No answer"
? "There are no memories containing the requested information."
: await Value.ChatCompletionService.GetChatMessageContentAsync($"You are an assistant queried for information. Use this data: {answerValue} to answer the question: {args.Content}.");
ChatHistory.AddAssistantMessage(messageContent);
args.SendMessage(new Message(MessageRole.Assistant, messageContent));
}
Here, we intercept the message, query the SemanticTextMemory
, and use the results to generate an answer with the chat completion service.
This was a long post, but I hope it’s useful for you all. Until next time—XAF OUT!
You can find the full implementation on this repo
by Joche Ojeda | Oct 15, 2024 | A.I, Semantic Kernel, XAF, XPO
A few weeks ago, I forked the Semantic Kernel repository to experiment with it. One of my first experiments was to create a memory provider for XPO. The task was not too difficult; basically, I needed to implement the IMemoryStore interface, add some XPO boilerplate code, and just like that, we extended the Semantic Kernel memory store to support 10+ databases. You can check out the code for the XpoMemoryStore here.
My initial goal in creating the XpoMemoryStore was simply to see if XPO would be a good fit for handling embeddings. Spoiler alert: it was! To understand the basic functionality of the plugin, you can take a look at the integration test here.
As you can see, usage is straightforward. You start by connecting to the database that handles embedding collections, and all you need is a valid XPO connection string:
using XpoMemoryStore db = await XpoMemoryStore.ConnectAsync("XPO connection string");
In my original design, everything worked fine, but I faced some challenges when trying to use my new XpoMemoryStore in XAF. Here’s what I encountered:
- The implementation of XpoMemoryStore uses its own data layer, which can lead to issues. This needs to be rewritten to use the same data layer as XAF.
- The XpoEntry implementation cannot be extended. In some use cases, you might want to use a different object to store the embeddings, perhaps one that has an association with another object.
To address these problems, I introduced the IXpoEntryManager interface. The goal of this interface is to handle object creation and queries.
public interface IXpoEntryManager
{
T CreateObject();
public event EventHandler ObjectCreatedEvent;
void Commit();
IQueryable GetQuery(bool inTransaction = true);
void Delete(object instance);
void Dispose();
}
Now, object creation is handled through the CreateObject<T>
method, allowing the underlying implementation to be changed to use a UnitOfWork
or ObjectSpace
. There’s also the ObjectCreatedEvent
event, which lets you access the newly created object in case you need to associate it with another object. Lastly, the GetQuery<T>
method enables redirecting the search for records to a different type.
I’ll keep updating the code as needed. If you’d like to discuss AI, XAF, or .NET, feel free to schedule a meeting: Schedule a Meeting with us.
Until next time, XAF out!
Related Article
https://www.jocheojeda.com/2024/09/04/using-the-imemorystore-interface-and-devexpress-xpo-orm-to-implement-a-custom-memory-store-for-semantic-kernel/