<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~files/atom-premium.xsl"?>
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:feedpress="https://feed.press/xmlns" xmlns:media="http://search.yahoo.com/mrss/" xmlns:podcast="https://podcastindex.org/namespace/1.0">
  <feedpress:locale>en</feedpress:locale>
  <feedpress:newsletterId>telerik-blogs</feedpress:newsletterId>
  <link rel="hub" href="https://feedpress.superfeedr.com/"/>
  <logo>https://static.feedpress.com/logo/telerik-blogs-5aafd3c47efc3.jpg</logo>
  <title type="text">Telerik Blogs</title>
  <subtitle type="text">The official blog of Progress Telerik - expert articles and tutorials for developers.</subtitle>
  <id>uuid:c843a650-d943-45c9-9639-9d00ed9f5884;id=3861</id>
  <updated>2026-05-08T00:44:47Z</updated>
  <link rel="alternate" href="https://www.telerik.com/"/>
  <link rel="self" type="application/atom+xml" href="https://feeds.telerik.com/blogs"/>
  <entry>
    <id>urn:uuid:7111c2e6-ad81-4433-b3d6-85e3d9e6e745</id>
    <title type="text">Design Principles Unpacked, No. 4: Balance</title>
    <summary type="text">Balance creates stability, but it's not the finish line. Harmony composes differences toward a shared purpose—in layouts, teams and life.</summary>
    <published>2026-05-07T17:12:01Z</published>
    <updated>2026-05-08T00:44:47Z</updated>
    <author>
      <name>Teon Beijl </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/design-principles-unpacked-no-4-balance"/>
    <content type="text"><![CDATA[<p><span class="featured">Balance creates stability, but it's not the finish line. Harmony composes differences toward a shared purpose&mdash;in layouts, teams and life.</span></p><p>Balance is one of those principles everyone talks about, but few question. To be honest, it might be the most subjective principle designers use. It&rsquo;s definitely the one where I&rsquo;ve said in the past: &ldquo;balance feels off.&rdquo;</p><p>And balance matters. It reduces chaos and creates stability. Without balance, design becomes overwhelming and distracting.</p><p>So balance isn&rsquo;t wrong. But I think it&rsquo;s incomplete.</p><p>Because balance focuses on stability. Reducing tension between elements until everything feels still.</p><p>And that focus on stillness is costly. It ignores the reason why you&rsquo;re designing. It can dismiss the goal in favor of calmness.</p><h2 id="the-value-of-balance">The Value of Balance</h2><p>So, balance does a lot of things well.</p><p>Balance is the principle that keeps design from falling apart. It gives structure. It prevents visual distraction. It makes layouts feel polished.</p><p>When a design feels chaotic, balance is usually what&rsquo;s missing.</p><p>In life, it&rsquo;s the same. Balance is what keeps a team from burning out. What keeps a conversation from becoming a monologue.</p><p>Balance is necessary. But it&rsquo;s not enough.</p><h2 id="the-limit-of-equilibrium">The Limit of Equilibrium</h2><p>I worked on simulation software for production optimization in the oil and gas industry. Specifically, a feature to model how fluids move through earth formations. I remember the day a wise petrophysicist gave me a masterclass in fluid dynamics. One term stuck with me: <em>hydrostatic equilibrium</em>.</p><p>It&rsquo;s the theoretical state where all fluids have settled into still, horizontal layers. Nothing moving. Everything at rest.</p><p>We used it as a mathematical baseline&mdash;a zero-state from which we could calculate actual movements. But the truth is, we needed that zero-state because there&rsquo;s no way to calculate without one.</p><p>And that&rsquo;s the limit. The zero-state is useful as a reference point. But chasing it as a goal dismisses reality. There is always movement. It never settles.</p><p>The same is true in design. When we focus only on balance, we treat every element as if it should settle into place. We calm things down. We reduce tension. And that works&mdash;until it flattens too much.</p><p>A heading doesn&rsquo;t serve the same purpose as a caption. A warning isn&rsquo;t the same as a confirmation.</p><p>Balance can reduce the differences between elements. But that can reduce the effectiveness as well. That&rsquo;s a problem.</p><h2 id="from-balance-to-harmony">From Balance to Harmony</h2><p>So if balance alone isn&rsquo;t enough, what&rsquo;s the next step? <strong>Harmony.</strong></p><p>Balance focuses on stability. Harmony focuses on purpose.</p><p>Where balance settles the elements, harmony connects them. Not by making them the same, but by making them resonate.</p><p>A harmonious layout isn&rsquo;t one where everything is calm. It&rsquo;s one where every element contributes to a shared whole. Where differences aren&rsquo;t flattened, but intentionally leveraged.</p><p>In a balanced layout, the logic is: <em>reduce the tension</em>. In a harmonious layout, the logic is: <em>make it work together</em>.</p><p>One calms things down. The other connects them.</p><h2 id="what-i-learned-about-sound-waves">What I Learned About Sound Waves</h2><p>Years ago, I spent a few weeks at the Royal Conservatory in The Hague. I was studying sonology, chasing my dreams. That didn&rsquo;t last long&mdash;I dropped out. But I learned something about sound that changed how I think about design.</p><p>Sound is made of waves. Every note is a frequency. A vibration with its own character, its own behavior. They are moving elements.</p><p>Low frequencies are deep and wide. High frequencies are sharp and precise. They&rsquo;re fundamentally different.</p><p>And that&rsquo;s exactly why they work together.</p><p>The beauty of composition&mdash;in music&mdash;is the art and science of making different waves serve a shared goal. You don&rsquo;t flatten a bass line to match the treble. You don&rsquo;t mute the high notes to let the low ones dominate. You compose them. You give each frequency its own space, its own role and its own moment.</p><p>When you compose, something happens. The sound becomes beautiful. Purposeful.</p><p>Each wave keeps its own characteristics. But together, they form something none of them could produce alone.</p><p>This principle extends far beyond interfaces and sound.</p><p>Harmony also means giving each person room to contribute what only they can contribute. Not by flattening their differences, but by connecting them toward a shared purpose.</p><p>When that happens in a team, the same thing happens as in music.</p><h2 id="when-balance-feels-off">When Balance Feels Off</h2><p>So when balance feels off, there is dissonance. Two elements are in each other&rsquo;s way.</p><p>The instinct is to calm things down. To settle it. And sometimes that&rsquo;s the right call.</p><p>But sometimes the answer isn&rsquo;t less tension. It&rsquo;s better connection. Finding how two things that look too different can compose into a relationship that serves a larger objective.</p><p>Each in their unique role. Some loud, some soft. Some long, some short. All playing their part.</p><h2 id="takeaways">Takeaways</h2><p>Balance is valuable and stable. But balance alone can lead us to settle. Settle for stillness when we should be composing for purpose.</p><p>Harmony does that. It doesn&rsquo;t just ask: <em>Are we stable?</em> It asks: <em>Does it serve the goal?</em></p><p>Harmony is about working together by leveraging uniqueness. Not settling for order alone, but differentiating with intent.</p><p>So the next time you&rsquo;re designing a layout, building a team or even navigating a relationship, don&rsquo;t stop at balance. Ask whether everything resonates.</p><p>Because when elements are composed&mdash;not just balanced&mdash;the result is harmony. On purpose. By design.</p><aside><hr data-sf-ec-immutable="" /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">Read more from Teon</h4></div><div class="col-8"><p class="u-fs16 u-mb0">Teon Beijl often writes from real experiences as a design lead in the oil and gas industry. Check out <a target="_blank" href="https://www.telerik.com/blogs/design-operator-ux-design-can-improve-decisions-high-stakes-environments">how UX design can improve decisions in high-stakes environments</a> and <a target="_blank" href="https://www.telerik.com/blogs/out-control-design-guide-alarm-management">alarm management</a>.</p></div></div></aside>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:bb2fa99e-0c29-407f-9eb9-4d2b43a9632a</id>
    <title type="text">Creating More Realistic Tests with In-Memory Databases in ASP.NET Core</title>
    <summary type="text">Testing ASP.NET Core APIs with in-memory SQLite and JustMock enables validation of real-world scenarios like pagination, keys and rules without a real database.</summary>
    <published>2026-05-06T17:20:57Z</published>
    <updated>2026-05-08T00:44:47Z</updated>
    <author>
      <name>Assis Zang </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/creating-more-realistic-tests-memory-databases-aspnet-core"/>
    <content type="text"><![CDATA[<p><span class="featured">Testing ASP.NET Core APIs with in-memory SQLite and JustMock enables validation of real-world scenarios like pagination, keys and rules without a real database.</span></p><p>Testing a web API involves much more than simply checking whether methods return or send data correctly. It requires validating real-world behaviors like pagination, unique keys, data persistence and other database rules. Anticipating and reproducing these scenarios can be challenging, especially when they depend on specific query behaviors or stored data.</p><p>To address this, the .NET ecosystem provides powerful tools for unit testing. In this post, we explore how in-memory SQLite serves as an effective solution for testing ASP.NET Core APIs, enabling developers to simulate common scenarios without deploying a real database. Additionally, we demonstrate how tools like Progress Telerik JustMock can streamline test implementation and help validate business rules efficiently.</p><h2 id="the-importance-of-testing-in-real-world-environments">The Importance of Testing in Real-World Environments</h2><p>A common example of functionality in web APIs is paginated search, which at first glance is quite simple. We receive parameters such as <code>page</code> and <code>pageSize</code>, apply a Skip and a Take, return the data and move on. In a controlled scenario, with few records, it&rsquo;s difficult for something to go wrong.</p><p>The problem begins when this code reaches production or even test environments, without having been validated with a real database. Consider a common scenario: in a paginated search, sorting is essential for it to function correctly. Without an explicit <code>OrderBy</code>, the database does not guarantee the order of the returned records.</p><p>In in-memory lists, the result usually appears stable, which completely masks the problem. Without tests running this query on a relational database, the error goes unnoticed, resulting in inconsistent pagination with duplicate records between pages, items &ldquo;disappearing&rdquo; when navigating between pages or different results for the same request. This type of bug is especially difficult to reproduce locally when not using a real database or even a simulation of one.</p><p>This is where the in-memory database plays a very important role. It allows the query to be executed exactly as it will be executed in production, revealing flaws that only appear when pagination, sorting and the database work together.</p><h2 id="understanding-in-memory-sqlite">Understanding In-Memory SQLite</h2><p>In-memory SQLite is a way to use the SQLite database entirely in memory. Instead of saving data to a <code>.db</code> file, SQLite creates the database only while the application or connection is active. When the connection is closed, all data is discarded.</p><p>In-memory SQLite is widely used in automated testing, especially in ASP.NET Core applications in conjunction with Entity Framework Core, as it has advanced features such as simulating a real database and executing real SQL (constraints, indexes, unique keys, FK and others).</p><h2 id="testing-with-sqlite-in-memory">Testing with SQLite in Memory</h2><p>Next, we&rsquo;ll look at some examples of unit tests in common web API scenarios where in-memory SQLite excels. But first, let&rsquo;s create the basic application and then add the tests.</p><p>You can access all the code discussed in this post in this GitHub repository: <a target="_blank" href="https://github.com/zangassis/memo-order">Memo Order source code</a>.</p><p>To create the application you can use the command below:</p><pre class=" language-bash"><code class="prism  language-bash">dotnet new web -o MemoOrder
</code></pre><p>To add the NuGet packages you can use the following commands:</p><pre class=" language-bash"><code class="prism  language-bash">dotnet add package Microsoft.EntityFrameworkCore --version 10.0.1
dotnet add package Microsoft.EntityFrameworkCore.Sqlite --version 10.0.1
</code></pre><p>Then, open the application and create the following classes:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">namespace</span> MemoOrder<span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Order</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token keyword">int</span> Id <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword">public</span> <span class="token keyword">string</span> Number <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">string</span><span class="token punctuation">.</span>Empty<span class="token punctuation">;</span>
    <span class="token keyword">public</span> <span class="token keyword">decimal</span> TotalAmount <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword">public</span> DateTime CreatedAt <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword">public</span> <span class="token keyword">bool</span> IsDeleted <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">namespace</span> MemoOrder<span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">OrderSummaryDto</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token keyword">string</span> Number <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword">public</span> <span class="token keyword">decimal</span> Total <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">using</span> Microsoft<span class="token punctuation">.</span>EntityFrameworkCore<span class="token punctuation">;</span>

<span class="token keyword">namespace</span> MemoOrder<span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">AppDbContext</span> <span class="token punctuation">:</span> DbContext
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token function">AppDbContext</span><span class="token punctuation">(</span>DbContextOptions<span class="token operator">&lt;</span>AppDbContext<span class="token operator">&gt;</span> options<span class="token punctuation">)</span>
        <span class="token punctuation">:</span> <span class="token keyword">base</span><span class="token punctuation">(</span>options<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span>

    <span class="token keyword">public</span> DbSet<span class="token operator">&lt;</span>Order<span class="token operator">&gt;</span> Orders <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token generic-method function">Set<span class="token punctuation">&lt;</span>Order<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">protected</span> <span class="token keyword">override</span> <span class="token keyword">void</span> <span class="token function">OnModelCreating</span><span class="token punctuation">(</span>ModelBuilder modelBuilder<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        modelBuilder<span class="token punctuation">.</span><span class="token generic-method function">Entity<span class="token punctuation">&lt;</span>Order<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span>entity <span class="token operator">=</span><span class="token operator">&gt;</span>
        <span class="token punctuation">{</span>
            entity<span class="token punctuation">.</span><span class="token function">HasKey</span><span class="token punctuation">(</span>o <span class="token operator">=</span><span class="token operator">&gt;</span> o<span class="token punctuation">.</span>Id<span class="token punctuation">)</span><span class="token punctuation">;</span>
            entity<span class="token punctuation">.</span><span class="token function">Property</span><span class="token punctuation">(</span>o <span class="token operator">=</span><span class="token operator">&gt;</span> o<span class="token punctuation">.</span>Number<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">IsRequired</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            entity<span class="token punctuation">.</span><span class="token function">HasIndex</span><span class="token punctuation">(</span>o <span class="token operator">=</span><span class="token operator">&gt;</span> o<span class="token punctuation">.</span>Number<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">IsUnique</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
entity<span class="token punctuation">.</span><span class="token function">HasQueryFilter</span><span class="token punctuation">(</span>o <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token operator">!</span>o<span class="token punctuation">.</span>IsDeleted<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">using</span> Microsoft<span class="token punctuation">.</span>EntityFrameworkCore<span class="token punctuation">;</span>

<span class="token keyword">namespace</span> MemoOrder<span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">OrderRepository</span>
<span class="token punctuation">{</span>
    <span class="token keyword">private</span> <span class="token keyword">readonly</span> AppDbContext _context<span class="token punctuation">;</span>

    <span class="token keyword">public</span> <span class="token function">OrderRepository</span><span class="token punctuation">(</span>AppDbContext context<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        _context <span class="token operator">=</span> context<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">AddAsync</span><span class="token punctuation">(</span>Order order<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        _context<span class="token punctuation">.</span>Orders<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span>order<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">await</span> _context<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">async</span> Task<span class="token operator">&lt;</span>List<span class="token operator">&lt;</span>Order<span class="token operator">&gt;</span><span class="token operator">&gt;</span> <span class="token function">Pagination</span><span class="token punctuation">(</span>AppDbContext context<span class="token punctuation">,</span> <span class="token keyword">int</span> skip<span class="token punctuation">,</span> <span class="token keyword">int</span> take<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">return</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span>Orders
                    <span class="token punctuation">.</span><span class="token function">OrderBy</span><span class="token punctuation">(</span>o <span class="token operator">=</span><span class="token operator">&gt;</span> o<span class="token punctuation">.</span>Id<span class="token punctuation">)</span>
                    <span class="token punctuation">.</span><span class="token function">Skip</span><span class="token punctuation">(</span>skip<span class="token punctuation">)</span>
                    <span class="token punctuation">.</span><span class="token function">Take</span><span class="token punctuation">(</span>take<span class="token punctuation">)</span>
                    <span class="token punctuation">.</span><span class="token function">ToListAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Now let&rsquo;s create a special factory class that will be used in the tests to create the in-memory database and keep the connection open while the tests are running. Create the following class in the application:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">namespace</span> MemoOrder<span class="token punctuation">;</span>

<span class="token keyword">using</span> Microsoft<span class="token punctuation">.</span>Data<span class="token punctuation">.</span>Sqlite<span class="token punctuation">;</span>
<span class="token keyword">using</span> Microsoft<span class="token punctuation">.</span>EntityFrameworkCore<span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">SqliteInMemoryFactory</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token keyword">static</span> AppDbContext <span class="token function">Create</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">var</span> connection <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">SqliteConnection</span><span class="token punctuation">(</span><span class="token string">"DataSource=:memory:"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        connection<span class="token punctuation">.</span><span class="token function">Open</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">var</span> options <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">DbContextOptionsBuilder</span><span class="token operator">&lt;</span>AppDbContext<span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">UseSqlite</span><span class="token punctuation">(</span>connection<span class="token punctuation">)</span>
            <span class="token punctuation">.</span>Options<span class="token punctuation">;</span>

        <span class="token keyword">var</span> context <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">AppDbContext</span><span class="token punctuation">(</span>options<span class="token punctuation">)</span><span class="token punctuation">;</span>
        context<span class="token punctuation">.</span>Database<span class="token punctuation">.</span><span class="token function">EnsureCreated</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">return</span> context<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Note that to tell SQLite to use an in-memory database, we use <code>DataSource=:memory:</code> in the connection string.</p><h3 id="creating-the-test-project">Creating the Test Project</h3><p>All the code needed to implement the unit tests is ready, so let&rsquo;s create the test project and download the NuGet packages for it. To do this, you can use the following commands, simply run them in a terminal in the application&rsquo;s root directory:</p><pre class=" language-bash"><code class="prism  language-bash">dotnet new xunit -n MemoOrder.Tests
<span class="token function">cd</span> MemoOrder.Tests
dotnet add package Microsoft.NET.Test.Sdk --version 18.0.1
dotnet add package xunit --version 2.9.3
dotnet add package xunit.runner.visualstudio --version 3.1.5
dotnet add package coverlet.collector --version 6.0.4
dotnet add reference <span class="token punctuation">..</span>/MemoOrder/MemoOrder.csproj
</code></pre><p>Next, inside the project MemoOrder.Tests, create a new class called OrderRepositoryTests and let&rsquo;s add tests to it.</p><h3 id="testing-persistence">Testing Persistence</h3><p>The first test will be used to validate persistence&mdash;that is, to insert data into the database (which in this case will be the in-memory database). To do this, simply add the following test method to the <code>OrderRepositoryTests</code> class:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token punctuation">[</span>Fact<span class="token punctuation">]</span>
<span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">AddAsync_ShouldPersistOrder</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token comment">// Arrange</span>
    <span class="token keyword">using</span> <span class="token keyword">var</span> context <span class="token operator">=</span> SqliteInMemoryFactory<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">var</span> repository <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">OrderRepository</span><span class="token punctuation">(</span>context<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">var</span> order <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Order</span>
    <span class="token punctuation">{</span>
        Number <span class="token operator">=</span> <span class="token string">"ORD-2026-0001"</span><span class="token punctuation">,</span>
        TotalAmount <span class="token operator">=</span> 200m<span class="token punctuation">,</span>
        CreatedAt <span class="token operator">=</span> DateTime<span class="token punctuation">.</span>UtcNow
    <span class="token punctuation">}</span><span class="token punctuation">;</span>

    <span class="token comment">// Act</span>
    <span class="token keyword">await</span> repository<span class="token punctuation">.</span><span class="token function">AddAsync</span><span class="token punctuation">(</span>order<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// Assert</span>
    <span class="token keyword">var</span> savedOrder <span class="token operator">=</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span>Orders<span class="token punctuation">.</span><span class="token function">FirstAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    Assert<span class="token punctuation">.</span><span class="token function">Equal</span><span class="token punctuation">(</span><span class="token string">"ORD-2026-0001"</span><span class="token punctuation">,</span> savedOrder<span class="token punctuation">.</span>Number<span class="token punctuation">)</span><span class="token punctuation">;</span>
    Assert<span class="token punctuation">.</span><span class="token function">Equal</span><span class="token punctuation">(</span>200m<span class="token punctuation">,</span> savedOrder<span class="token punctuation">.</span>TotalAmount<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>Note that we use <code>var context = SqliteInMemoryFactory.Create();</code> to create and represent the database in memory. It works exactly like the EF Core context that maps database tables to class entities.</p><h3 id="testing-constraint-unique">Testing Constraint UNIQUE</h3><p>Another common mistake is allowing duplicate records to reach the production environment, which in many cases can have catastrophic impacts. With in-memory testing, we can anticipate problems like this. Simply create a test that verifies inserting a duplicate record will generate an exception. In this case, we could do the following:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token punctuation">[</span>Fact<span class="token punctuation">]</span>
<span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">Should_Throw_When_Duplicated_Order_Number</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">using</span> <span class="token keyword">var</span> context <span class="token operator">=</span> SqliteInMemoryFactory<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    context<span class="token punctuation">.</span>Orders<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Order</span>
    <span class="token punctuation">{</span>
        Number <span class="token operator">=</span> <span class="token string">"ORD-001"</span><span class="token punctuation">,</span>
        TotalAmount <span class="token operator">=</span> <span class="token number">100</span><span class="token punctuation">,</span>
        CreatedAt <span class="token operator">=</span> DateTime<span class="token punctuation">.</span>UtcNow
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">await</span> context<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    context<span class="token punctuation">.</span>Orders<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Order</span>
    <span class="token punctuation">{</span>
        Number <span class="token operator">=</span> <span class="token string">"ORD-001"</span><span class="token punctuation">,</span>
        TotalAmount <span class="token operator">=</span> <span class="token number">200</span><span class="token punctuation">,</span>
        CreatedAt <span class="token operator">=</span> DateTime<span class="token punctuation">.</span>UtcNow
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">await</span> Assert<span class="token punctuation">.</span><span class="token generic-method function">ThrowsAsync<span class="token punctuation">&lt;</span>DbUpdateException<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">&gt;</span> context<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>In this test, we created an instance of the database in memory, added an item to the table and saved it.</p><p>Then, we tried to add the same item, which generates an exception because, in the dbContext configuration, we declared the Number property of the Order entity as Unique: <code>entity.HasIndex(o =&gt; o.Number).IsUnique();</code>.</p><p>Thus, we have a real-world scenario, demonstrating that duplicate records will not reach the production environment, as the unit test is able to reproduce exactly the same result.</p><h3 id="testing-pagination">Testing Pagination</h3><p>To test pagination using an in-memory database, we could do the following:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token punctuation">[</span>Fact<span class="token punctuation">]</span>
<span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">Should_Return_Paginated_Orders</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">using</span> <span class="token keyword">var</span> context <span class="token operator">=</span> SqliteInMemoryFactory<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">var</span> repository <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">OrderRepository</span><span class="token punctuation">(</span>context<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">&lt;=</span> <span class="token number">10</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        context<span class="token punctuation">.</span>Orders<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Order</span>
        <span class="token punctuation">{</span>
            Number <span class="token operator">=</span> $<span class="token string">"ORD-{i}"</span><span class="token punctuation">,</span>
            TotalAmount <span class="token operator">=</span> i <span class="token operator">*</span> <span class="token number">10</span><span class="token punctuation">,</span>
            CreatedAt <span class="token operator">=</span> DateTime<span class="token punctuation">.</span>UtcNow
        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">await</span> context<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    List<span class="token operator">&lt;</span>Order<span class="token operator">&gt;</span> page <span class="token operator">=</span> <span class="token keyword">await</span> repository<span class="token punctuation">.</span><span class="token function">Pagination</span><span class="token punctuation">(</span>context<span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    Assert<span class="token punctuation">.</span><span class="token function">Equal</span><span class="token punctuation">(</span><span class="token number">5</span><span class="token punctuation">,</span> page<span class="token punctuation">.</span>Count<span class="token punctuation">)</span><span class="token punctuation">;</span>
    Assert<span class="token punctuation">.</span><span class="token function">Equal</span><span class="token punctuation">(</span><span class="token string">"ORD-6"</span><span class="token punctuation">,</span> page<span class="token punctuation">.</span><span class="token function">First</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>Number<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>Note that here we create 10 items and insert them into the in-memory database. Then, we use the <code>Pagination(context, 5, 5)</code> method to perform the pagination and finally verify if the expected quantity and item are correct.</p><p>An in-memory database wouldn&rsquo;t be mandatory to test pagination. This could be done using only regular in-memory lists. However, in this way, we can reproduce the same production scenario.</p><h3 id="testing-soft-delete">Testing Soft Delete</h3><p>Soft-delete is a technique where data is not physically removed from the database, but rather marked as inactive through a flag, such as an <code>isDeleted</code> or <code>deleted_at</code> column. This allows you to keep the entire transaction history in the database.</p><p>The problem is that in some cases soft delete can leak unwanted data into reports, new endpoints or even refactored queries. To prevent this from happening, we can use the in-memory database and simulate a search with and without soft-delete:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token punctuation">[</span>Fact<span class="token punctuation">]</span>
 <span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">SoftDeleted_Orders_Should_Not_Appear_In_Default_Queries</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
 <span class="token punctuation">{</span>
     <span class="token keyword">using</span> <span class="token keyword">var</span> context <span class="token operator">=</span> SqliteInMemoryFactory<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

     context<span class="token punctuation">.</span>Orders<span class="token punctuation">.</span><span class="token function">AddRange</span><span class="token punctuation">(</span>
         <span class="token keyword">new</span> <span class="token class-name">Order</span>
         <span class="token punctuation">{</span>
             Number <span class="token operator">=</span> <span class="token string">"ORD-001"</span><span class="token punctuation">,</span>
             TotalAmount <span class="token operator">=</span> <span class="token number">100</span><span class="token punctuation">,</span>
             CreatedAt <span class="token operator">=</span> DateTime<span class="token punctuation">.</span>UtcNow<span class="token punctuation">,</span>
             IsDeleted <span class="token operator">=</span> <span class="token keyword">false</span>
         <span class="token punctuation">}</span><span class="token punctuation">,</span>
         <span class="token keyword">new</span> <span class="token class-name">Order</span>
         <span class="token punctuation">{</span>
             Number <span class="token operator">=</span> <span class="token string">"ORD-002"</span><span class="token punctuation">,</span>
             TotalAmount <span class="token operator">=</span> <span class="token number">200</span><span class="token punctuation">,</span>
             CreatedAt <span class="token operator">=</span> DateTime<span class="token punctuation">.</span>UtcNow<span class="token punctuation">,</span>
             IsDeleted <span class="token operator">=</span> <span class="token keyword">true</span>
         <span class="token punctuation">}</span>
     <span class="token punctuation">)</span><span class="token punctuation">;</span>

     <span class="token keyword">await</span> context<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

     <span class="token keyword">var</span> orders <span class="token operator">=</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span>Orders<span class="token punctuation">.</span><span class="token function">ToListAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

     Assert<span class="token punctuation">.</span><span class="token function">Single</span><span class="token punctuation">(</span>orders<span class="token punctuation">)</span><span class="token punctuation">;</span>
     Assert<span class="token punctuation">.</span><span class="token function">Equal</span><span class="token punctuation">(</span><span class="token string">"ORD-001"</span><span class="token punctuation">,</span> orders<span class="token punctuation">.</span><span class="token function">First</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>Number<span class="token punctuation">)</span><span class="token punctuation">;</span>
 <span class="token punctuation">}</span>
</code></pre><p>In this test, the list returns only one item because the second one is marked as deleted. This happens because we declared the query filter <code>entity.HasQueryFilter(o =&gt; !o.IsDeleted);</code> when the database is created. By using an in-memory database, we can verify this mechanism works.</p><p>It&rsquo;s also possible to ignore the query filter and still verify it works:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token punctuation">[</span>Fact<span class="token punctuation">]</span>
<span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">Admin_Query_Should_See_SoftDeleted_Orders</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">using</span> <span class="token keyword">var</span> context <span class="token operator">=</span> SqliteInMemoryFactory<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    context<span class="token punctuation">.</span>Orders<span class="token punctuation">.</span><span class="token function">AddRange</span><span class="token punctuation">(</span>
        <span class="token keyword">new</span> <span class="token class-name">Order</span>
        <span class="token punctuation">{</span>
            Number <span class="token operator">=</span> <span class="token string">"ORD-001"</span><span class="token punctuation">,</span>
            TotalAmount <span class="token operator">=</span> <span class="token number">100</span><span class="token punctuation">,</span>
            CreatedAt <span class="token operator">=</span> DateTime<span class="token punctuation">.</span>UtcNow<span class="token punctuation">,</span>
            IsDeleted <span class="token operator">=</span> <span class="token keyword">false</span>
        <span class="token punctuation">}</span><span class="token punctuation">,</span>
        <span class="token keyword">new</span> <span class="token class-name">Order</span>
        <span class="token punctuation">{</span>
            Number <span class="token operator">=</span> <span class="token string">"ORD-002"</span><span class="token punctuation">,</span>
            TotalAmount <span class="token operator">=</span> <span class="token number">200</span><span class="token punctuation">,</span>
            CreatedAt <span class="token operator">=</span> DateTime<span class="token punctuation">.</span>UtcNow<span class="token punctuation">,</span>
            IsDeleted <span class="token operator">=</span> <span class="token keyword">true</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">await</span> context<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">var</span> orders <span class="token operator">=</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span>Orders<span class="token punctuation">.</span><span class="token function">IgnoreQueryFilters</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToListAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    Assert<span class="token punctuation">.</span><span class="token function">Equal</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> orders<span class="token punctuation">.</span>Count<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><h3 id="testing-rollback">Testing Rollback</h3><p>When performing multiple operations, we run the risk of something failing during the process and the data becoming inconsistent. To keep this from happening, we can use the in-memory database to test if the rollback operation solves this problem.</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token punctuation">[</span>Fact<span class="token punctuation">]</span>
<span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">Transaction_Should_Rollback_When_Error_Occurs</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">using</span> <span class="token keyword">var</span> context <span class="token operator">=</span> SqliteInMemoryFactory<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">using</span> <span class="token keyword">var</span> transaction <span class="token operator">=</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span>Database<span class="token punctuation">.</span><span class="token function">BeginTransactionAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    context<span class="token punctuation">.</span>Orders<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Order</span>
    <span class="token punctuation">{</span>
        Number <span class="token operator">=</span> <span class="token string">"ORD-NMBR-1"</span><span class="token punctuation">,</span>
        TotalAmount <span class="token operator">=</span> <span class="token number">100</span><span class="token punctuation">,</span>
        CreatedAt <span class="token operator">=</span> DateTime<span class="token punctuation">.</span>UtcNow
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">await</span> context<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">await</span> transaction<span class="token punctuation">.</span><span class="token function">RollbackAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">var</span> count <span class="token operator">=</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span>Orders<span class="token punctuation">.</span><span class="token function">CountAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    Assert<span class="token punctuation">.</span><span class="token function">Equal</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> count<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>In this test, we added an item to the order list, saved it and then performed a rollback. Finally, we verified that the list was empty, checking that the operation was undone.</p><h3 id="testing-query-refactoring">Testing Query Refactoring</h3><p>Refactoring queries is often necessary, whether to improve performance or simply to make the code cleaner. The problem is that this can break queries that previously worked.</p><p>To see that both the old and new queries have the same result, we can use an in-memory database as demonstrated in the example below:</p><pre class=" language-csharp"><code class="prism  language-csharp">   <span class="token punctuation">[</span>Fact<span class="token punctuation">]</span>
    <span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">Refactored_Query_Should_Return_Same_Result</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">using</span> <span class="token keyword">var</span> context <span class="token operator">=</span> SqliteInMemoryFactory<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">var</span> oldResult <span class="token operator">=</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span>Orders
            <span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>o <span class="token operator">=</span><span class="token operator">&gt;</span> o<span class="token punctuation">.</span>TotalAmount <span class="token operator">&gt;</span> <span class="token number">100</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>o <span class="token operator">=</span><span class="token operator">&gt;</span> o<span class="token punctuation">.</span>Id<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">ToListAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">var</span> newResult <span class="token operator">=</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span>Orders
            <span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>o <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token keyword">new</span> <span class="token punctuation">{</span> o<span class="token punctuation">.</span>Id<span class="token punctuation">,</span> o<span class="token punctuation">.</span>TotalAmount <span class="token punctuation">}</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>o <span class="token operator">=</span><span class="token operator">&gt;</span> o<span class="token punctuation">.</span>TotalAmount <span class="token operator">&gt;</span> <span class="token number">100</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>o <span class="token operator">=</span><span class="token operator">&gt;</span> o<span class="token punctuation">.</span>Id<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">ToListAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        Assert<span class="token punctuation">.</span><span class="token function">Equal</span><span class="token punctuation">(</span>oldResult<span class="token punctuation">,</span> newResult<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
</code></pre><p>In this example, we verify that both queries have the same result, so even after refactoring, the old logic will continue to work.</p><h3 id="query-test-with-projection-to-dto">Query Test with Projection to DTO</h3><p>When using Data Transfer Objects (DTOs) for data transport, we are subject to errors that can silently break our queries. Again, with an in-memory database, we can simulate a query using a DTO.</p><pre class=" language-csharp"><code class="prism  language-csharp">   <span class="token punctuation">[</span>Fact<span class="token punctuation">]</span>
    <span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">Projection_To_Dto_Should_Work</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">using</span> <span class="token keyword">var</span> context <span class="token operator">=</span> SqliteInMemoryFactory<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">var</span> result <span class="token operator">=</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span>Orders
            <span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>o <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token keyword">new</span> <span class="token class-name">OrderSummaryDto</span>
            <span class="token punctuation">{</span>
                Number <span class="token operator">=</span> o<span class="token punctuation">.</span>Number<span class="token punctuation">,</span>
                Total <span class="token operator">=</span> o<span class="token punctuation">.</span>TotalAmount
            <span class="token punctuation">}</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">ToListAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        Assert<span class="token punctuation">.</span><span class="token function">NotNull</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
</code></pre><p>Note that through this test, we can be sure that our DTOs will function correctly.</p><h2 id="not-all-tests-need-a-database">Not All Tests Need a Database</h2><p>Until now, we&rsquo;ve used in-memory SQLite to validate constraints (UNIQUE), global filters (Soft Delete), real pagination, GroupBy, Sum, transactions and rollback.</p><p>Tests like these depend on database behavior, but it&rsquo;s common to encounter scenarios where the database isn&rsquo;t present, and only business rules are involved. In this case, to facilitate our work and validate our tests, we can use tools like <a target="_blank" href="https://www.telerik.com/products/mocking.aspx">JustMock</a> and simulate a production-facing environment.</p><p>Next, we&rsquo;ll look at some examples of business rule tests where we can use JustMock to easily create mocks for our tests.</p><h3 id="verifying-the-repository-will-be-called">Verifying the Repository Will Be Called</h3><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token punctuation">[</span>Fact<span class="token punctuation">]</span>
<span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">CreateAsync_Should_Call_Repository_AddAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token comment">// Arrange</span>
    <span class="token keyword">var</span> repository <span class="token operator">=</span> Mock<span class="token punctuation">.</span><span class="token generic-method function">Create<span class="token punctuation">&lt;</span>IOrderRepository<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">var</span> order <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Order</span>
    <span class="token punctuation">{</span>
        Id <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">,</span>
        Number <span class="token operator">=</span> <span class="token string">"ORD-001"</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>

    Mock<span class="token punctuation">.</span><span class="token function">Arrange</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">&gt;</span> repository<span class="token punctuation">.</span><span class="token function">AddAsync</span><span class="token punctuation">(</span>order<span class="token punctuation">)</span><span class="token punctuation">)</span>
        <span class="token punctuation">.</span><span class="token function">Returns</span><span class="token punctuation">(</span>Task<span class="token punctuation">.</span>CompletedTask<span class="token punctuation">)</span>
        <span class="token punctuation">.</span><span class="token function">MustBeCalled</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">var</span> service <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">OrderService</span><span class="token punctuation">(</span>repository<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// Act</span>
    <span class="token keyword">await</span> service<span class="token punctuation">.</span><span class="token function">CreateAsync</span><span class="token punctuation">(</span>order<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// Assert</span>
    Mock<span class="token punctuation">.</span><span class="token function">Assert</span><span class="token punctuation">(</span>repository<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><h3 id="verifying-the-gethighvalueorders-method-returns-only-orders-above-the-minimum-value">Verifying the GetHighValueOrders Method Returns Only Orders Above the Minimum Value</h3><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token punctuation">[</span>Fact<span class="token punctuation">]</span>
<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">GetHighValueOrders_Should_Return_Only_Orders_Above_MinValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token comment">// Arrange</span>
    <span class="token keyword">var</span> repository <span class="token operator">=</span> Mock<span class="token punctuation">.</span><span class="token generic-method function">Create<span class="token punctuation">&lt;</span>IOrderRepository<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">var</span> orders <span class="token operator">=</span> <span class="token function">FakeOrders</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
        <span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>o <span class="token operator">=</span><span class="token operator">&gt;</span> o<span class="token punctuation">.</span>TotalAmount <span class="token operator">&gt;=</span> <span class="token number">150</span> <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>o<span class="token punctuation">.</span>IsDeleted<span class="token punctuation">)</span>
        <span class="token punctuation">.</span><span class="token function">ToList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    Mock<span class="token punctuation">.</span><span class="token function">Arrange</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">&gt;</span> repository<span class="token punctuation">.</span><span class="token function">GetHighValueOrders</span><span class="token punctuation">(</span><span class="token number">150</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
        <span class="token punctuation">.</span><span class="token function">Returns</span><span class="token punctuation">(</span>orders<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">var</span> service <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">OrderService</span><span class="token punctuation">(</span>repository<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// Act</span>
    <span class="token keyword">var</span> result <span class="token operator">=</span> service<span class="token punctuation">.</span><span class="token function">GetHighValueOrders</span><span class="token punctuation">(</span><span class="token number">150</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// Assert</span>
    Assert<span class="token punctuation">.</span><span class="token function">AreEqual</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> result<span class="token punctuation">.</span>Count<span class="token punctuation">)</span><span class="token punctuation">;</span>
    Assert<span class="token punctuation">.</span><span class="token function">IsTrue</span><span class="token punctuation">(</span>result<span class="token punctuation">.</span><span class="token function">First</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>TotalAmount <span class="token operator">&gt;=</span> <span class="token number">150</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><h3 id="verifying-the-service-forwards-exactly-the-filter-received">Verifying the Service Forwards Exactly the Filter Received</h3><pre class=" language-csharp"><code class="prism  language-csharp">       <span class="token punctuation">[</span>Fact<span class="token punctuation">]</span>
        <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">GetHighValueOrders_Should_Call_Repository_With_Same_MinValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
        <span class="token punctuation">{</span>
            <span class="token comment">// Arrange</span>
            <span class="token keyword">var</span> repository <span class="token operator">=</span> Mock<span class="token punctuation">.</span><span class="token generic-method function">Create<span class="token punctuation">&lt;</span>IOrderRepository<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

            Mock<span class="token punctuation">.</span><span class="token function">Arrange</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">&gt;</span> repository<span class="token punctuation">.</span><span class="token function">GetHighValueOrders</span><span class="token punctuation">(</span><span class="token number">500</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
                <span class="token punctuation">.</span><span class="token function">Returns</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">List</span><span class="token operator">&lt;</span>Order<span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
                <span class="token punctuation">.</span><span class="token function">MustBeCalled</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

            <span class="token keyword">var</span> service <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">OrderService</span><span class="token punctuation">(</span>repository<span class="token punctuation">)</span><span class="token punctuation">;</span>

            <span class="token comment">// Act</span>
            service<span class="token punctuation">.</span><span class="token function">GetHighValueOrders</span><span class="token punctuation">(</span><span class="token number">500</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

            <span class="token comment">// Assert</span>
            Mock<span class="token punctuation">.</span><span class="token function">Assert</span><span class="token punctuation">(</span>repository<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
</code></pre><p>If you run all the tests, you can verify that they all passed:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/all-tests-passed.png?sfvrsn=60e45a83_2" title="all tests passed" alt="All tests passed" /></p><h2 id="conclusion">Conclusion</h2><p>Testing web applications is common in software development, but these tests don&rsquo;t always guarantee that errors won&rsquo;t occur in the production environment. Using in-memory databases, such as in-memory SQLite, allows you to reproduce scenarios closer to reality.</p><p>In this post, we saw how this approach helps identify common errors, such as persistence, pagination and projection issues. Furthermore, we explored how JustMock can help save time when validating business rules. I hope the content covered here helps you further improve your applications, making them more reliable through even more realistic testing.</p><hr /><p><a href="https://www.telerik.com/try/justmock" target="_blank">Try JustMock</a> free for 30 days, or <a href="https://www.telerik.com/try/devcraft-ultimate" target="_blank">try out the whole Telerik DevCraft</a> suite for access to the ASP.NET Core component library and lots more (also a free 30-day trial).</p>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:2f875edd-30d6-4e50-a69a-3d5c40e28d42</id>
    <title type="text">Creating a Custom AI Agent with Telerik Tools 5: Creating an Interactive UI in JavaScript</title>
    <summary type="text">If you’re going to give your users access to an AI-enabled backend in JavaScript, you need to give them an AI-appropriate frontend.</summary>
    <published>2026-05-06T16:39:24Z</published>
    <updated>2026-05-08T00:44:47Z</updated>
    <author>
      <name>Peter Vogel </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/creating-custom-ai-agent-telerik-tools-5-creating-interactive-ui-javascript"/>
    <content type="text"><![CDATA[<p><span class="featured">If you&rsquo;re going to give your users access to an AI-enabled backend in JavaScript, you need to give them an AI-appropriate frontend.</span></p><p>Right now, using Progress Telerik and Kendo UI tools you can create your own custom AI agent. (I&rsquo;ve covered those tools in earlier posts, beginning with <a href="https://www.telerik.com/blogs/creating-custom-ai-agent-telerik-tools-1-configuring-llm-azure-ollama" target="_blank">configuring a Large Language Model (LLM) in Azure or Ollama</a>, <a href="https://www.telerik.com/blogs/loading-accessing-converting-office-pdf-documents-telerik-document-processing-libraries" target="_blank">loading content with Telerik Document Processing Library</a>, linking your <a href="https://www.telerik.com/blogs/creating-custom-ai-agent-telerik-tools-2-loading-accessing-agent-content" target="_blank">content to your LLM</a>&nbsp;and <a href="https://www.telerik.com/blogs/creating-custom-ai-agent-telerik-tools-3-summarizing-querying" target="_blank">summarizing/querying your content</a>. But that backend isn&rsquo;t much use without a frontend your users can interact with.</p><p>Your users have expectations around what the UI for an AI agent look like and how that UI should behave&mdash;expectations that are set by tools like OpenAI&rsquo;s <a target="_blank" href="https://chatgpt.com/">ChatGPT</a> and <a target="_blank" href="https://copilot.microsoft.com/">Microsoft&rsquo;s Copilot</a> clients. Specifically, your users are looking for an interactive flow that enables them to evolve (through a set of prompts) from some initial response to a response that meets their needs. The Progress&nbsp;<a target="_blank" href="https://www.telerik.com/design-system/docs/components/aiprompt/">AIPrompt</a> provides that UI as a single component.</p><p>This post is about how to create that UI with the <a target="_blank" href="https://demos.telerik.com/kendo-ui/aiprompt/index">JavaScript Kendo UI for jQuery version</a>.</p><blockquote><p>In an earlier post (<a href="https://www.telerik.com/blogs/creating-custom-ai-agent-telerik-tools-4-crafting-interactive-blazor-ui" target="_blank">Creating a Custom AI Agent with Telerik Tools 4: Crafting an Interactive Blazor UI</a>), I walked through implementing a UI with the <a href="https://www.telerik.com/blazor-ui/ai-prompt" target="_blank">Blazor version of the AIPrompt component</a>. And, in addition to the JavaScript for Kendo UI and Blazor versions of the AIPrompt, there are also versions for <a target="_blank" href="https://www.telerik.com/products/aspnet-ajax/ai-prompt.aspx">ASP.NET Ajax</a>, <a target="_blank" href="https://www.telerik.com/products/winforms/aiprompt.aspx">WinForms</a>&nbsp;and <a target="_blank" href="https://www.telerik.com/maui-ui/aiprompt">MAUI</a>.</p></blockquote><h2 id="background-the-custom-agent-backend-as-a-web-service">Background: The Custom Agent Backend as a Web Service</h2><p>If you just want to know how to use AIPrompt (and aren&rsquo;t interested in the AI-enabled backend I&rsquo;ll be using), you can skip this whole section.</p><p>To implement a JavaScript frontend, I had to wrap my custom AI agent in a web service that could be called from my JavaScript code. My processor expects two parameters: the user&rsquo;s prompt, of course (which I pass as a parameter to the single method that my AI agent class exposes), but also a processing option that lets the user choose between two modes: asking questions or summarizing selected content (that&rsquo;s controlled through a property on the class that implements my AI agent). Fundamentally, that processing mode chooses between two Telerik AI connectors: <a target="_blank" href="https://docs.telerik.com/devtools/document-processing/libraries/radpdfprocessing/features/gen-ai-powered-document-insights/complete-context-question-processor">CompleteContextQuestionProcessor</a> or <a target="_blank" href="https://docs.telerik.com/devtools/document-processing/libraries/radpdfprocessing/features/gen-ai-powered-document-insights/summarization-processor">SummarizationProcessor</a>.</p><p>I decided that, since my prompt could be a relatively wordy string, I&rsquo;d pass those two parameters as a JSON document in the body of a request to my web service. So, as part of creating my web service I defined this Data Transfer Object (DTO) class to hold those two pieces of information:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CustomAgentRequest</span>
<span class="token punctuation">{</span>
   <span class="token keyword">public</span> <span class="token keyword">string</span> Prompt <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">string</span><span class="token punctuation">.</span>Empty<span class="token punctuation">;</span>
   <span class="token keyword">public</span> <span class="token keyword">string</span> Mode <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">string</span><span class="token punctuation">.</span>Empty<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>I then created a web service wrapper as an ASP.NET Core WebAPI project that instantiates my AI agent&rsquo;s class and accepts my DTO class as part of an <code>HttpPut</code> method. (Since I was passing data in the body of the request, I had to use either a PUT or a POST request. I flipped a coin and settled on PUT.) In that <code>HttpPut</code> method, my service sets the processor&rsquo;s mode property and calls the single method on my AI agent class, passing the prompt and returning the text generated by my selected mode.</p><p>My web service wrapper looked something like this:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CustomAgentAPI</span> <span class="token punctuation">:</span> ControllerBase
<span class="token punctuation">{</span>
    CustomAgent<span class="token operator">?</span> proc<span class="token punctuation">;</span>

    <span class="token keyword">public</span> <span class="token function">CustomAgentAPI</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        proc <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    
    <span class="token punctuation">[</span>HttpPut<span class="token punctuation">]</span>
    <span class="token keyword">public</span> <span class="token keyword">async</span> Task<span class="token operator">&lt;</span>IActionResult<span class="token operator">&gt;</span> <span class="token function">Put</span><span class="token punctuation">(</span>CustomAgentRequest req<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
       proc<span class="token punctuation">.</span>Mode <span class="token operator">=</span> req<span class="token punctuation">.</span>Mode<span class="token punctuation">;</span> 
       <span class="token keyword">return</span> <span class="token function">Ok</span><span class="token punctuation">(</span><span class="token keyword">await</span> proc<span class="token punctuation">.</span><span class="token function">ProcessRequest</span><span class="token punctuation">(</span>req<span class="token punctuation">.</span>Prompt<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>I then added this JavaScript function to my frontend&rsquo;s webpage that accepts a prompt and a mode option, and then uses JavaScript&rsquo;s Fetch API functionality to call my web service:</p><pre class=" language-javascript"><code class="prism  language-javascript"><span class="token keyword">const</span> putAgentPrompt <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span>requestPrompt<span class="token punctuation">,</span> requestProcess<span class="token punctuation">)</span> <span class="token operator">=&gt;</span>
<span class="token punctuation">{</span>
   <span class="token keyword">const</span> resp <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span>
               <span class="token string">"&lt;URL for my Web service&gt;"</span><span class="token punctuation">,</span>
               <span class="token punctuation">{</span>
                method<span class="token punctuation">:</span> <span class="token string">"PUT"</span><span class="token punctuation">,</span>
                headers<span class="token punctuation">:</span> 
                <span class="token punctuation">{</span>
                   <span class="token string">"Content-Type"</span><span class="token punctuation">:</span> <span class="token string">"application/json"</span>
                <span class="token punctuation">}</span><span class="token punctuation">,</span>
                body<span class="token punctuation">:</span> JSON<span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span>
                <span class="token punctuation">{</span>
                   Prompt<span class="token punctuation">:</span> requestPrompt<span class="token punctuation">,</span>
                   Mode<span class="token punctuation">:</span> requestProcess
                <span class="token punctuation">}</span>
   <span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
   <span class="token keyword">return</span> <span class="token keyword">await</span> resp<span class="token punctuation">.</span><span class="token function">text</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>Since JavaScript&rsquo;s Fetch API is asynchronous, I had to use the <code>await</code> keyword when calling the <code>fetch</code> function and mark my <code>putAgentPrompt</code> function as <code>async</code>.</p><p>My JavaScript function extracts the <code>text</code> property of the response object returned from my web service and then returns that to the JavaScript application that called my function.</p><h2 id="processing-requests">Processing Requests</h2><p>Assuming you&rsquo;ve created a project with the necessary Progress <a target="_blank" href="https://www.telerik.com/kendo-jquery-ui/documentation/intro/first-steps">Kendo UI support</a>, the markup for using the AIPrompt component is very simple&mdash;a <code>div</code> element with its <code>id</code> attribute set to some name of your choice:</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>customAgent<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
</code></pre><p>To load that <code>div</code> element with the Progress AIPrompt component, you use jQuery to find that element and then call the Kendo UI for jQuery <code>kendoAIPrompt</code> extension from the found element. The resulting UI looks like this, with a textbox for the user to enter their prompt and a button to trigger sending the prompt to my backend:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-03/aiprompt-javascript-1---basic-ui.png?sfvrsn=6bc63813_2" alt="The basic AI Prompt display showing a textbox with a “Generate” button underneath it. Above the textbox are two additional buttons: “Ask AI” (which is currently highlighted) and “Output”" /></p><p>To enable the UI to process your user&rsquo;s prompts, you must pass the <code>kendoAIPrompt</code> extension a single parameter, an object literal. To process the user prompts when the user clicks the Generate button, you must set that object literal&rsquo;s <code>promptRequest</code> property to a function that, in turn, accepts a single parameter.</p><p>That means that, initially, the code to process a user&rsquo;s prompt looks something like this:</p><pre class=" language-javascript"><code class="prism  language-javascript"><span class="token function">$</span><span class="token punctuation">(</span><span class="token string">"#customAgent"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">kendoAIPrompt</span><span class="token punctuation">(</span>        
      <span class="token punctuation">{</span>
   promptRequest<span class="token punctuation">:</span> <span class="token keyword">async</span> <span class="token keyword">function</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span> 
   <span class="token punctuation">{</span>

         <span class="token punctuation">}</span>
     <span class="token punctuation">}</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>    
</code></pre><p>If you&rsquo;re tempted to write the function set in the <code>promptRequest</code> property as an arrow function, <em>don&rsquo;t</em> give into the temptation. Your function in the <code>promptRequest</code> property must accept the <code>this</code> reference set in the <code>kendoAIPrompt</code> function, which is only possible if you use a traditional JavaScript function.</p><p>The parameter passed to your <code>promptRequest</code> has two properties that you&rsquo;re interested in:</p><ul><li><code>prompt</code>: The text the user entered in the AIPrompt&rsquo;s prompt view</li><li><code>isRetry</code>: Set to <code>false</code> unless the user clicks the Retry button displayed when AIPrompt shows the result of your processing in AIPrompt&rsquo;s output view</li></ul><p>Within your <code>promptRequest</code> function, you need to call your custom AI agent (in my case, the web service that wraps my AI agent class). I used the <code>putAgentPrompt</code> function from earlier in this post which returns a single text result.</p><p>After that, you need to provide the information that the AIPrompt&rsquo;s output view requires and then switch to that output view. Building the output view consists of loading an object literal with four properties (all required):</p><ul><li><code>id</code>: A unique identifier</li><li><code>output</code>: The response from your backend</li><li><code>prompt</code>: The user&rsquo;s prompt</li><li><code>isRetry</code>: The <code>isRetry</code> setting</li></ul><p>Once you&rsquo;ve built that object, you just pass it to the <code>kendoAIPrompt</code>&rsquo;s <code>addPromptOutput</code> function to have your response added to <code>kendoAIPrompt</code>&rsquo;s output view.</p><p>Finally, to actually display the AIPrompt&rsquo;s output view, you call the <code>kendoAIPrompt</code>&rsquo;s <code>activeView</code> function, passing the name of the view you want (&ldquo;output&rdquo;, in this case).</p><p>Putting that all together, a typical version of the <code>promptRequest</code> function would look like this:</p><pre class=" language-javascript"><code class="prism  language-javascript"><span class="token function">$</span><span class="token punctuation">(</span><span class="token string">"#customAgent"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">kendoAIPrompt</span><span class="token punctuation">(</span> 
    <span class="token punctuation">{</span>
        promptRequest<span class="token punctuation">:</span> <span class="token keyword">async</span> <span class="token keyword">function</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span> 
         <span class="token punctuation">{</span> 
             <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">addPromptOutput</span><span class="token punctuation">(</span>
                         <span class="token punctuation">{</span>
                 id<span class="token punctuation">:</span> kendo<span class="token punctuation">.</span><span class="token function">guid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
                output<span class="token punctuation">:</span> <span class="token keyword">await</span> <span class="token function">putAgentPrompt</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>prompt<span class="token punctuation">,</span> <span class="token string">"summarize"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
                prompt<span class="token punctuation">:</span> e<span class="token punctuation">.</span>prompt<span class="token punctuation">,</span>
                 isRetry<span class="token punctuation">:</span> <span class="token boolean">false</span>
                        <span class="token punctuation">}</span>
             <span class="token punctuation">)</span><span class="token punctuation">;</span>
             <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">activeView</span><span class="token punctuation">(</span><span class="token string">"output"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
         <span class="token punctuation">}</span>
   <span class="token punctuation">}</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>A typical response might look like this, showing the user&rsquo;s response and the output from my custom AI agent:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-03/aiprompt-javascript-2--initial-response.png?sfvrsn=30c75aed_2" alt="The AIPrompt’s output view. The output button is now highlighted and the textbox is gone, replaced with the text the user entered before clicking the Generate button. Below that is a large block of text with Markdown markup embedded in it." /></p><p>However, as you can see, the result isn&rsquo;t necessarily very pretty because, as responses get more interesting, my custom agent produces output that uses <a target="_blank" href="https://www.markdownguide.org/">Markdown</a> for formatting. However, AIPrompt&rsquo;s default output page just displays all that text &ldquo;as is.&rdquo;</p><h2 id="taking-control-of-the-output">Taking Control of the Output</h2><p>You can, however, replace AIPrompt&rsquo;s default output view with your own custom view and, in that custom, convert the output to HTML.</p><p>You do that by setting the <code>outputTemplate</code> property of the parameter passed to the <code>kendoAIPrompt</code> function to yet another function. That <code>outputTemplate</code> function will be passed a parameter that has a <code>content</code> property, which holds the response you put in the <code>output</code> property back in your <code>promptRequest</code> function.</p><p>To support a better display, I added the <code>markdown-it</code> JavaScript library to my page with this <code>script</code> tag and CDN URL:</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>https://cdnjs.cloudflare.com/ajax/libs/markdown-it/13.0.1/markdown-it.min.js<span class="token punctuation">"</span></span> 
              <span class="token attr-name">integrity</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>sha512-SYfDUYPg5xspsG6OOpXU366G8SZsdHOhqk/icdrYJ2E/WKZxPxze7d2HD3AyXpT7U22PZ5y74xRpqZ6A2bJ+kQ==<span class="token punctuation">"</span></span> 
              <span class="token attr-name">crossorigin</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>anonymous<span class="token punctuation">"</span></span> 
              <span class="token attr-name">referrerpolicy</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>no-referrer<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token script language-javascript"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>In my custom output template, I could then use the library&rsquo;s <code>markdownit</code> function that returns an object with a <code>render</code> function that converts Markdown to HTML. All I had to do was pass the <code>content</code> property of the parameter passed to my <code>outputTemplate</code> to that <code>render</code> function.</p><p>This means that extending the <code>kendoAIPrompt</code> function with a custom function that converts my output to HTML looks like this:</p><pre class=" language-javascript"><code class="prism  language-javascript"><span class="token function">$</span><span class="token punctuation">(</span><span class="token string">"#customAgent"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">kendoAIPrompt</span><span class="token punctuation">(</span>        <span class="token punctuation">{</span>
            promptRequest<span class="token punctuation">:</span> <span class="token keyword">async</span> <span class="token keyword">function</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span> 
            <span class="token punctuation">{</span> 
                &hellip;previous code&hellip;
            <span class="token punctuation">}</span><span class="token punctuation">,</span>

            outputTemplate<span class="token punctuation">:</span> output   <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
                    <span class="token keyword">return</span> <span class="token function">markdownit</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">render</span><span class="token punctuation">(</span>output<span class="token punctuation">.</span>content<span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token punctuation">}</span>
         <span class="token punctuation">}</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>The enhanced output looks like this:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-03/aiprompt-javascript-3--enhanced-response.png?sfvrsn=8815bc5e_2" alt="A repeat of the previous graphic but, this time, the solid block of text-with-markup has been replaced with formatted text including headings and bullet points" /></p><h2 id="allowing-the-user-to-customize-their-prompts">Allowing the User to Customize Their Prompts</h2><p>Your agent probably supports some customization options that your users will want to take advantage of (for example, my agent supports two modes: summarizing and querying). You have two options for letting your users customize how your backend responds to your users&rsquo; prompts:</p><ul><li>Set options on your backend. My custom agent, for example, operates in two modes, depending on which of the Telerik AI connectors my code invokes.</li><li>Tweak the prompts being sent to your backend in order to trigger specific behavior</li></ul><p>In AIPrompt, I can support the user selecting my backend&rsquo;s mode by having AIPrompt display a commands menu which I&rsquo;ll use to let the user choose which mode (summarize or query) they want. To trigger that commands menu, I need to add a commands view to the <code>kendoAIPrompt</code>&rsquo;s <code>views</code> collection and load that view with an array of the menu items to be displayed in the component&rsquo;s command menu.</p><p>However, if you add a custom view to AIPrompt&rsquo;s <code>views</code> then you have to add implementations for AIPrompt&rsquo;s existing, predefined prompt view and output view. You add those default views to the <code>views</code> property of the parameter object passed to <code>kendoAIPrompt</code>, like this:</p><pre class=" language-javascript"><code class="prism  language-javascript"><span class="token function">$</span><span class="token punctuation">(</span><span class="token string">"#customAgent"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">kendoAIPrompt</span><span class="token punctuation">(</span>        <span class="token punctuation">{</span>
        promptRequest<span class="token punctuation">:</span> <span class="token keyword">async</span> <span class="token keyword">function</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span> 
        <span class="token punctuation">{</span> 
   &hellip;existing code&hellip;
        <span class="token punctuation">}</span><span class="token punctuation">,</span>
        views<span class="token punctuation">:</span> <span class="token punctuation">[</span> 
            <span class="token punctuation">{</span>
               type<span class="token punctuation">:</span> <span class="token string">'prompt'</span><span class="token punctuation">,</span>
            <span class="token punctuation">}</span><span class="token punctuation">,</span>
            <span class="token punctuation">{</span>
               type<span class="token punctuation">:</span> <span class="token string">'output'</span><span class="token punctuation">,</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">]</span>
<span class="token punctuation">}</span> 
</code></pre><p>To add a command menu, you tack another view definition on to the end of your new default views, setting its <code>type</code> property to &ldquo;commands&rdquo;. In addition, you set this view&rsquo;s <code>promptCommands</code> property to an array of objects with these properties:</p><ul><li><code>id</code>: A unique value within the array</li><li><code>text</code>: The text that will be displayed to the user in AIPrompt&rsquo;s commands menu</li><li><code>prompt</code>: Associated text (probably text you will send to your agent backend&mdash;optional)</li><li><code>icon</code>: An SVG graphic (you can draw from the Progress <a target="_blank" href="https://www.telerik.com/design-system/docs/foundation/iconography/icon-list/">store of icons</a>&mdash;also, optional)</li></ul><p>Here, I&rsquo;ve set up four options for the user to select from:</p><pre class=" language-javascript"><code class="prism  language-javascript">views<span class="token punctuation">:</span> <span class="token punctuation">[</span> 
    <span class="token punctuation">{</span>
       type<span class="token punctuation">:</span> <span class="token string">'prompt'</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">{</span>
       type<span class="token punctuation">:</span> <span class="token string">'output'</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">{</span>
       type<span class="token punctuation">:</span> <span class="token string">'commands'</span><span class="token punctuation">,</span> 
       promptCommands<span class="token punctuation">:</span> <span class="token punctuation">[</span>
           <span class="token punctuation">{</span> id<span class="token punctuation">:</span> <span class="token string">"Summarize"</span><span class="token punctuation">,</span> text<span class="token punctuation">:</span> <span class="token string">"Summarize"</span><span class="token punctuation">,</span> prompt<span class="token punctuation">:</span> <span class="token string">"in 100 words"</span><span class="token punctuation">,</span> 
        icon<span class="token punctuation">:</span> <span class="token string">"info-circle"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
           <span class="token punctuation">{</span> id<span class="token punctuation">:</span> <span class="token string">"SummarizeTerse"</span><span class="token punctuation">,</span> text<span class="token punctuation">:</span> <span class="token string">"Summarize: Terse"</span><span class="token punctuation">,</span> prompt<span class="token punctuation">:</span> <span class="token string">"in 50 words, "</span><span class="token punctuation">,</span> 
             icon<span class="token punctuation">:</span> <span class="token string">"min-width"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
           <span class="token punctuation">{</span> id<span class="token punctuation">:</span> <span class="token string">"SummarizeVerbose"</span><span class="token punctuation">,</span> text<span class="token punctuation">:</span> <span class="token string">"Summarize: Verbose"</span><span class="token punctuation">,</span> prompt<span class="token punctuation">:</span> <span class="token string">"in 200 words"</span><span class="token punctuation">,</span> 
        icon<span class="token punctuation">:</span> <span class="token string">"max-width"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
           <span class="token punctuation">{</span> id<span class="token punctuation">:</span> <span class="token string">"Ask"</span><span class="token punctuation">,</span> text<span class="token punctuation">:</span> <span class="token string">"Ask a question"</span><span class="token punctuation">,</span> 
             icon<span class="token punctuation">:</span> <span class="token string">"question-circle"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
        <span class="token punctuation">]</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">]</span><span class="token punctuation">,</span>
</code></pre><p>The result is that the AIPrompt gets a new overflow menu icon in its command bar, just to the right of the existing &ldquo;Ask AI&rdquo; and &ldquo;Output&rdquo; buttons. When your user clicks on the overflow menu icon, they get your list of commands:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-03/aiprompt-javascript-4--comands-menu.png?sfvrsn=229dd1f6_2" alt="The AIPrompt’s initial view a new builder/menu overflow button (three horizontal dot) has been added to the right of the existing Output button in the toolbar. That icon is highlighted and, instead of displaying a textbox and Generate button, the AIPrompt is displaying a list of menu choices, displaying the icon and text from the code in the post" /></p><p>To respond to the user clicking on one of the command menu options, you need to load a function into the <code>commandExecute</code> property in the object passed to the <code>kendoAIPrompt</code> function. That <code>commandExecute</code> function will be passed a parameter that has an <code>item</code> property holding the object from the <code>promptCommands</code> array that the user selected.</p><p>You can decide what to do in the <code>commandExecute</code> function. You could, for example, just save the user&rsquo;s choice and use it later when the user clicks AIPrompt&rsquo;s Generate button.</p><p>I&rsquo;ll go a little further: In my <code>commandExecute</code> method, I&rsquo;ll submit the command to my custom agent and display the results, leveraging the user&rsquo;s last prompt.</p><p>To support that, in my <code>promptRequest</code> function, I save the user&rsquo;s last prompt in a variable called, cleverly, <code>lastPrompt</code>. I also create a variable called mode to hold which mode (&ldquo;Ask&rdquo; or &ldquo;Summarize&rdquo;) the user wants to operate in.</p><p>My revised <code>promptRequest</code> function that saves the user&rsquo;s prompt and uses my new mode variable looks like this:</p><pre class=" language-javascript"><code class="prism  language-javascript"><span class="token keyword">let</span> lastPrompt <span class="token operator">=</span> <span class="token string">""</span><span class="token punctuation">;</span>
<span class="token keyword">let</span> mode <span class="token operator">=</span> <span class="token string">"Ask"</span><span class="token punctuation">;</span>
    
<span class="token function">$</span><span class="token punctuation">(</span><span class="token string">"#customAgent"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">kendoAIPrompt</span><span class="token punctuation">(</span>
    <span class="token punctuation">{</span>
        promptRequest<span class="token punctuation">:</span> <span class="token keyword">async</span> <span class="token keyword">function</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span> 
        <span class="token punctuation">{</span>         
            <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">addPromptOutput</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
                id<span class="token punctuation">:</span> kendo<span class="token punctuation">.</span><span class="token function">guid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
                output<span class="token punctuation">:</span> <span class="token keyword">await</span> <span class="token function">putAgentPrompt</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>prompt<span class="token punctuation">,</span> mode<span class="token punctuation">)</span><span class="token punctuation">,</span>
                prompt<span class="token punctuation">:</span> e<span class="token punctuation">.</span>prompt<span class="token punctuation">,</span>
                isRetry<span class="token punctuation">:</span> e<span class="token punctuation">.</span>isRetry
        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  
        lastPrompt <span class="token operator">=</span> e<span class="token punctuation">.</span>prompt<span class="token punctuation">;</span>
        &hellip;  
</code></pre><p>Then, in my <code>commandExecute</code> function, I check which command the user picked from my commands menu. If the user selected the menu option that switches to &ldquo;Ask&rdquo; mode, I set my mode variable to &ldquo;Ask.&rdquo; If the user selected one of the summarization menu choices, I set the mode to &ldquo;Summarize&rdquo; and then modify the user&rsquo;s last prompt to include the <code>prompt</code> property from the selected menu choice.</p><p>Either way, I re-submit the user&rsquo;s last prompt, with the new settings, and display the result in AIPrompt&rsquo;s output view:</p><pre class=" language-javascript"><code class="prism  language-javascript"><span class="token function">$</span><span class="token punctuation">(</span><span class="token string">"#customAgent"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">kendoAIPrompt</span><span class="token punctuation">(</span>
    <span class="token punctuation">{</span>
        promptRequest<span class="token punctuation">:</span> <span class="token keyword">async</span> <span class="token keyword">function</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span> 
        <span class="token punctuation">{</span> 
           &hellip;previous code&hellip;
        <span class="token punctuation">}</span><span class="token punctuation">,</span>
        promptCommands<span class="token punctuation">:</span> <span class="token punctuation">{</span>
&hellip;command prompt menu choices&hellip;
        <span class="token punctuation">}</span><span class="token punctuation">,</span>
        commandExecute<span class="token punctuation">:</span> <span class="token keyword">async</span> <span class="token keyword">function</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span> 
       <span class="token punctuation">{</span>
            <span class="token keyword">let</span> newPrompt <span class="token operator">=</span> <span class="token string">""</span><span class="token punctuation">;</span>

            <span class="token keyword">if</span> <span class="token punctuation">(</span>c<span class="token punctuation">.</span>item<span class="token punctuation">.</span>id<span class="token punctuation">.</span><span class="token function">startsWith</span><span class="token punctuation">(</span><span class="token string">"Summarize"</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
            <span class="token punctuation">{</span>
                mode <span class="token operator">=</span> <span class="token string">"Summarize"</span><span class="token punctuation">;</span>
                newPrompt <span class="token operator">=</span> lastPrompt <span class="token operator">+</span> c<span class="token punctuation">.</span>item<span class="token punctuation">.</span>prompt<span class="token punctuation">;</span>
            <span class="token punctuation">}</span>
            <span class="token keyword">else</span>
            <span class="token punctuation">{</span>
                mode <span class="token operator">=</span> c<span class="token punctuation">.</span>item<span class="token punctuation">.</span>id<span class="token punctuation">;</span>
                newPrompt <span class="token operator">=</span> lastPrompt<span class="token punctuation">;</span>
            <span class="token punctuation">}</span>                

            <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">addPromptOutput</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
                id<span class="token punctuation">:</span> kendo<span class="token punctuation">.</span><span class="token function">guid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
                output<span class="token punctuation">:</span> <span class="token keyword">await</span> <span class="token function">putAgentPrompt</span><span class="token punctuation">(</span>newPrompt<span class="token punctuation">,</span> mode<span class="token punctuation">)</span><span class="token punctuation">,</span>
                prompt<span class="token punctuation">:</span> lastPrompt
             <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">activeView</span><span class="token punctuation">(</span><span class="token string">"output"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span><span class="token punctuation">,</span>    
</code></pre><p>The result looks like this, with the result of executing the menu choice added to the output from the user&rsquo;s previous requests:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-03/aiprompt-javascript-5--commands-with-multiple-output.png?sfvrsn=71c263e3_2" alt="AIPrompt’s output view showing two results: At the top is a 200 word summary that results from executing the “Summarize” menu choice; below that is the output from an earlier prompt requesting the minimum code to execute " /></p><h2 id="providing-sample-prompts">Providing Sample Prompts</h2><p>One last thing: It&rsquo;s not unusual for an AI agent&rsquo;s UI to include sample prompts to help users see what they can do with your application. Progress AIPrompt will let you do that, too, just by adding the <code>promptSuggestions</code> property to the object passed to <code>kendoAIPrompt</code> and setting it to an array of strings. This example loads three suggested prompts to AIPrompt:</p><pre class=" language-javascript"><code class="prism  language-javascript"><span class="token function">$</span><span class="token punctuation">(</span><span class="token string">"#customAgent"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">kendoAIPrompt</span><span class="token punctuation">(</span>
    <span class="token punctuation">{</span>
        promptRequest<span class="token punctuation">:</span> <span class="token keyword">async</span> <span class="token keyword">function</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span> 
        <span class="token punctuation">{</span> 
           &hellip;previous code&hellip;
        <span class="token punctuation">}</span><span class="token punctuation">,</span>
        promptSuggestions<span class="token punctuation">:</span> 
       <span class="token punctuation">[</span>
         <span class="token string">"Summarize, targeting project leads"</span><span class="token punctuation">,</span>             
        <span class="token string">"What are the key features"</span><span class="token punctuation">,</span>
         <span class="token string">"What are the critical steps"</span>
     <span class="token punctuation">]</span><span class="token punctuation">,</span>
     <span class="token punctuation">{</span>
</code></pre><p>The result looks like this:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-03/aiprompt-javascript-6--suggestions.png?sfvrsn=76c7a97_2" alt="The AIPrompt interface with the three suggested prompts defined in the sample code displayed in lozenge shaped buttons below the prompt textbox (and labeled Prompt Suggestion with an arrow that allows you to hide their display). The Generate button appears below the three suggestions" /></p><p>Now, when the user clicks on one of your suggested prompts, that prompt will be automatically copied into the prompt textbox where the user can modify the suggestion before clicking the Generate button. This can also simplify testing&mdash;instead of typing in a test prompt, you can just pick one of your suggestions.</p><p>Combining Telerik Document Processing Library and the library&rsquo;s AI Connectors lets you create your own custom AI Agent. And the AIPrompt, in any of its implementations, lets you create a front end that enables your users take advantage of your backend (and meets your users&rsquo; expectations). You&rsquo;re ready to give your users a whole new level of support.</p><hr /><p>Remember, you can get access to all of the Kendo UI and Telerik components in this series with a free 30-day trial of the <a target="_blank" href="https://www.telerik.com/devcraft">Telerik DevCraft</a> bundle.</p><p><a href="https://www.telerik.com/try/devcraft-ultimate" target="_blank" class="Btn">Try Now</a></p>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:2c9543f6-911a-443f-8612-e1d7e336574b</id>
    <title type="text">Routing Management and Creating NotFound Pages in Blazor</title>
    <summary type="text">Route handling got better in .NET 10. Let’s see how to create context for managing information on an error page and show users specific error pages.</summary>
    <published>2026-05-05T15:57:00Z</published>
    <updated>2026-05-08T00:44:47Z</updated>
    <author>
      <name>Héctor Pérez </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/routing-management-creating-notfound-pages-blazor"/>
    <content type="text"><![CDATA[<p><span class="featured">Route handling got better in .NET 10. Let&rsquo;s see how to create context for managing information on an error page and show users specific error pages.</span></p><p>If you have worked with Blazor in versions prior to .NET 10, you have certainly encountered some difficulties in implementing not found pages and in handling NavLink to show users the page they are currently on.</p><p>This is why, during the last update to .NET 10, the team behind Blazor has significantly improved the handling of routes and the management of not found pages. In this article, we will analyze the most important aspects. Let&rsquo;s get started!</p><h2 id="understanding-the-handling-of-not-found-routes-in-blazor">Understanding the Handling of Not Found Routes in Blazor</h2><p>When we talk about routing, we refer to the mechanism that allows the user to navigate to a specific component based on the requested URL. However, there may be times when there are non-existent URLs that the user attempts to navigate to, such as when an item has been deleted or when, for some reason, a page has been removed from the site.</p><p>Properly handling these scenarios is essential for a good user experience, good SEO and, in general, having a reliable web application.</p><h2 id="the-problem-with-not-found-pages-before-.net-10">The Problem with Not Found Pages Before .NET 10</h2><p>You should know that before .NET 10, as part of the <code>Router</code> component, there was a render fragment called <code>NotFound</code>, which at first glance could lead us to think that it allowed defining graphical code to show content when a route was not found:</p><pre class=" language-xml"><code class="prism  language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Router</span> <span class="token attr-name">AppAssembly</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>typeof(Program).Assembly<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Found</span> <span class="token attr-name">Context</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>routeData<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        ...
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>Found</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>NotFound</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>LayoutView</span> <span class="token attr-name">Layout</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>typeof(Layout.MainLayout)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">&gt;</span></span>Page not found<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>Sorry, but there is nothing here!<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>LayoutView</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>NotFound</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>Router</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>In practice, something different occurred, as when attempting to navigate to a non-existent page, an error page was displayed instead of the defined graphical code:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/net9-render-fragment-notfound-logic.gif?sfvrsn=d61310e6_2" alt="Execution flow of &#39;NotFound&#39; RenderFragment in .NET 9 routing" /></p><p>Another technique that was used before .NET 10 was to handle the not found page code within the components. For example, there could be an ecommerce-type application with a products page and a product details page. On the product details page, if the user tried to access a non-existent page, it was handled similarly to the following way:</p><pre class=" language-xml"><code class="prism  language-xml">@if (product == null)
{    
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>alert alert-warning text-center<span class="token punctuation">"</span></span> <span class="token attr-name">role</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>alert<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h4</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>alert-heading<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Product Not Found<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h4</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>The product with ID <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>strong</span><span class="token punctuation">&gt;</span></span>@Id<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>strong</span><span class="token punctuation">&gt;</span></span> does not exist in our catalog.<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>hr</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>btn btn-outline-primary<span class="token punctuation">"</span></span> <span class="token attr-name">@onclick</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>GoBack<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
            <span class="token entity" title="←">&amp;larr;</span> Back to Products
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
}
else
{
    ...
}
</code></pre><p>The execution of the previous code resulted in the following appearance:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/handling-notfound-errors-net9.png?sfvrsn=8f3d31f2_2" alt="Handling Not Found errors in .NET 9" /></p><p>The previous code, although it achieved the goal of showing the user that the product did not exist, had several problems:</p><ul><li>It returned an HTTP 200 code, not a 404.</li><li>Each component had to implement its own logic.</li><li>If there was navigation to a non-existent URL (not handled by a component), a site error page was shown.</li></ul><p>Next, let&rsquo;s see how .NET 10 improves the handling of not found pages.</p><h2 id="features-in-.net-10-for-route-management">Features in .NET 10 for Route Management</h2><p>In .NET 10, things have changed regarding route management. Let&rsquo;s analyze what these new features are.</p><h3 id="the-router.notfoundpage-parameter">The Router.NotFoundPage Parameter</h3><p>One of the most prominent updates is the addition of the <code>NotFoundPage</code> parameter to the <code>Router</code> component, which allows specifying a Razor page component that will be displayed when a route is not found. By default, in Blazor projects starting from .NET 10, in the <code>Pages</code> folder we find a component called <code>NotFound.razor</code>, whose structure is as follows:</p><p><strong>NotFound.razor</strong></p><pre class=" language-xml"><code class="prism  language-xml">@page "/not-found"
@layout MainLayout

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h3</span><span class="token punctuation">&gt;</span></span>Not Found<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h3</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>Sorry, the content you are looking for does not exist.<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
</code></pre><p><strong>Routes.razor</strong></p><pre class=" language-xml"><code class="prism  language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Router</span> <span class="token attr-name">AppAssembly</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>typeof(Program).Assembly<span class="token punctuation">"</span></span> <span class="token attr-name">NotFoundPage</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>typeof(Pages.NotFound)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
   ...
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>Router</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>The preview of this code gives the following result:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/default-notfoundpage-component-net10.png?sfvrsn=8d90d029_2" alt="Default NotFoundPage component behavior in .NET 10" /></p><p>In the code above, we can see that the 404 error page must include the <code>@page</code> directive for it to function correctly.</p><h3 id="customizing-the-notfound-component">Customizing the NotFound Component</h3><p>With the <code>NotFound.razor</code> component showing us how to implement a 404 page in our Blazor apps, the next step is to customize it. There are several ways to do this. For example, by adding support for contextual messages through a service class called <code>NotFoundContext</code>:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">NotFoundContext</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token keyword">string</span><span class="token operator">?</span> Heading <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword">public</span> <span class="token keyword">string</span><span class="token operator">?</span> Message <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">UpdateContext</span><span class="token punctuation">(</span><span class="token keyword">string</span> heading<span class="token punctuation">,</span> <span class="token keyword">string</span> message<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        Heading <span class="token operator">=</span> heading<span class="token punctuation">;</span>
        Message <span class="token operator">=</span> message<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>This class will allow you to show a custom heading and message based on the operation the user has requested. For it to work correctly, you need to register the service in <code>Program.cs</code>:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">var</span> builder <span class="token operator">=</span> WebApplication<span class="token punctuation">.</span><span class="token function">CreateBuilder</span><span class="token punctuation">(</span>args<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token generic-method function">AddScoped<span class="token punctuation">&lt;</span>NotFoundContext<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
<span class="token keyword">var</span> app <span class="token operator">=</span> builder<span class="token punctuation">.</span><span class="token function">Build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>Now, let&rsquo;s give a bit more design to the <code>NotFound.razor</code> template:</p><pre class=" language-xml"><code class="prism  language-xml">@page "/not-found"
@layout MainLayout
@using NotFoundManagementNET102.Services
@inject NotFoundContext NotFoundContext

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>PageTitle</span><span class="token punctuation">&gt;</span></span>Not Found<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>PageTitle</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>container mt-5<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>row justify-content-center<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>col-md-8 col-lg-6<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>card shadow-sm border-0 text-center<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>card-body p-5<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>mb-4<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg</span> <span class="token attr-name">xmlns</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>http://www.w3.org/2000/svg<span class="token punctuation">"</span></span> <span class="token attr-name">width</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>80<span class="token punctuation">"</span></span> <span class="token attr-name">height</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>80<span class="token punctuation">"</span></span> <span class="token attr-name">fill</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>#6c757d<span class="token punctuation">"</span></span>
                             <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>bi bi-exclamation-triangle<span class="token punctuation">"</span></span> <span class="token attr-name">viewBox</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>0 0 16 16<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>path</span> <span class="token attr-name">d</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>M7.938 2.016A.13.13 0 0 1 8.002 2a.13.13 0 0 1 .063.016...<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
                        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">&gt;</span></span>
                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>

                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h3</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>card-title text-dark mb-3<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                        @(NotFoundContext.Heading ?? "Page Not Found")
                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h3</span><span class="token punctuation">&gt;</span></span>

                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>card-text text-muted mb-4<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                        @(NotFoundContext.Message ?? "Sorry, the content you are looking for does not exist.")
                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>

                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>d-flex justify-content-center gap-2<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>/products<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>btn btn-primary<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                            <span class="token entity" title="←">&amp;larr;</span> Back to Products
                        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span>
                        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>/<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>btn btn-outline-secondary<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                            Go Home
                        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span>
                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>In the code above, you can notice that it checks whether the user sets a value for <code>Heading</code> and <code>Message</code>, in order to display either a generic message or a personalized one. Let&rsquo;s see next how to display custom pages based on the user&rsquo;s context.</p><h3 id="handling-not-found-pages-with-navigationmanager.notfound-and-onnotfound">Handling Not Found Pages with NavigationManager.NotFound() and OnNotFound</h3><p>In .NET 9, <code>NavigationManager.NotFound()</code> and <code>OnNotFound</code> were introduced as part of the framework improvements for handling not found pages. We can combine both with the <code>NotFoundContext</code> service to display a 404 response and custom messages, through the following code:</p><pre class=" language-csharp"><code class="prism  language-csharp">@page <span class="token string">"/product/{Id:int}"</span>
@<span class="token keyword">using</span> NotFoundManagementNET102<span class="token punctuation">.</span>Models
@<span class="token keyword">using</span> NotFoundManagementNET102<span class="token punctuation">.</span>Services
@implements <span class="token class-name">IDisposable</span>
@inject ProductService ProductService
@inject NavigationManager Navigation
@inject NotFoundContext NotFoundContext

<span class="token operator">&lt;</span>PageTitle<span class="token operator">&gt;</span>@<span class="token punctuation">(</span>product<span class="token operator">?</span><span class="token punctuation">.</span>Name <span class="token operator">?</span><span class="token operator">?</span> <span class="token string">"Product Detail"</span><span class="token punctuation">)</span><span class="token operator">&lt;</span><span class="token operator">/</span>PageTitle<span class="token operator">&gt;</span>

@<span class="token keyword">if</span> <span class="token punctuation">(</span>product <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"container mt-4"</span><span class="token operator">&gt;</span>
        <span class="token operator">&lt;</span>nav aria<span class="token operator">-</span>label<span class="token operator">=</span><span class="token string">"breadcrumb"</span><span class="token operator">&gt;</span>
            <span class="token operator">&lt;</span>ol <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"breadcrumb"</span><span class="token operator">&gt;</span>
                <span class="token operator">&lt;</span>li <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"breadcrumb-item"</span><span class="token operator">&gt;</span><span class="token operator">&lt;</span>a href<span class="token operator">=</span><span class="token string">"/products"</span><span class="token operator">&gt;</span>Products<span class="token operator">&lt;</span><span class="token operator">/</span>a<span class="token operator">&gt;</span><span class="token operator">&lt;</span><span class="token operator">/</span>li<span class="token operator">&gt;</span>
                <span class="token operator">&lt;</span>li <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"breadcrumb-item active"</span> aria<span class="token operator">-</span>current<span class="token operator">=</span><span class="token string">"page"</span><span class="token operator">&gt;</span>@product<span class="token punctuation">.</span>Name<span class="token operator">&lt;</span><span class="token operator">/</span>li<span class="token operator">&gt;</span>
            <span class="token operator">&lt;</span><span class="token operator">/</span>ol<span class="token operator">&gt;</span>
        <span class="token operator">&lt;</span><span class="token operator">/</span>nav<span class="token operator">&gt;</span>

        <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"card shadow-sm"</span><span class="token operator">&gt;</span>
            <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"card-body"</span><span class="token operator">&gt;</span>
                <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"row"</span><span class="token operator">&gt;</span>
                    <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"col-md-8"</span><span class="token operator">&gt;</span>
                        <span class="token operator">&lt;</span>h2 <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"card-title"</span><span class="token operator">&gt;</span>@product<span class="token punctuation">.</span>Name<span class="token operator">&lt;</span><span class="token operator">/</span>h2<span class="token operator">&gt;</span>
                        <span class="token operator">&lt;</span>span <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"badge bg-secondary mb-3"</span><span class="token operator">&gt;</span>@product<span class="token punctuation">.</span>Category<span class="token operator">&lt;</span><span class="token operator">/</span>span<span class="token operator">&gt;</span>
                        <span class="token operator">&lt;</span>p <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"card-text text-muted"</span><span class="token operator">&gt;</span>@product<span class="token punctuation">.</span>Description<span class="token operator">&lt;</span><span class="token operator">/</span>p<span class="token operator">&gt;</span>

                        <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"row mt-4"</span><span class="token operator">&gt;</span>
                            <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"col-6"</span><span class="token operator">&gt;</span>
                                <span class="token operator">&lt;</span>h5<span class="token operator">&gt;</span>Price<span class="token operator">&lt;</span><span class="token operator">/</span>h5<span class="token operator">&gt;</span>
                                <span class="token operator">&lt;</span>p <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"fs-4 text-primary fw-bold"</span><span class="token operator">&gt;</span>$@product<span class="token punctuation">.</span>Price<span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token string">"N2"</span><span class="token punctuation">)</span><span class="token operator">&lt;</span><span class="token operator">/</span>p<span class="token operator">&gt;</span>
                            <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
                            <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"col-6"</span><span class="token operator">&gt;</span>
                                <span class="token operator">&lt;</span>h5<span class="token operator">&gt;</span>Stock<span class="token operator">&lt;</span><span class="token operator">/</span>h5<span class="token operator">&gt;</span>
                                <span class="token operator">&lt;</span>span <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"badge @(product.Stock &gt; 30 ? "</span>bg<span class="token operator">-</span>success<span class="token string">" : product.Stock &gt; 10 ? "</span>bg<span class="token operator">-</span>warning<span class="token string">" : "</span>bg<span class="token operator">-</span>danger<span class="token string">") fs-6"</span><span class="token operator">&gt;</span>
                                    @product<span class="token punctuation">.</span>Stock units
                                <span class="token operator">&lt;</span><span class="token operator">/</span>span<span class="token operator">&gt;</span>
                            <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
                        <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
                    <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
                <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
            <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
            <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"card-footer"</span><span class="token operator">&gt;</span>
                <span class="token operator">&lt;</span>TelerikButton OnClick<span class="token operator">=</span><span class="token string">"GoBack"</span>
                               ThemeColor<span class="token operator">=</span><span class="token string">"@ThemeConstants.Button.ThemeColor.Primary"</span><span class="token operator">&gt;</span>
                    <span class="token operator">&amp;</span>larr<span class="token punctuation">;</span> Back to Products
                <span class="token operator">&lt;</span><span class="token operator">/</span>TelerikButton<span class="token operator">&gt;</span>
            <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
        <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
<span class="token punctuation">}</span>

@code <span class="token punctuation">{</span>
    <span class="token punctuation">[</span>Parameter<span class="token punctuation">]</span>
    <span class="token keyword">public</span> <span class="token keyword">int</span> Id <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>

    <span class="token keyword">private</span> Product<span class="token operator">?</span> product<span class="token punctuation">;</span>

    <span class="token keyword">protected</span> <span class="token keyword">override</span> <span class="token keyword">void</span> <span class="token function">OnInitialized</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token comment">// 1.</span>
        Navigation<span class="token punctuation">.</span>OnNotFound <span class="token operator">+</span><span class="token operator">=</span> HandleNotFound<span class="token punctuation">;</span>

        <span class="token comment">// 2.</span>
        product <span class="token operator">=</span> ProductService<span class="token punctuation">.</span><span class="token function">GetProductById</span><span class="token punctuation">(</span>Id<span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">if</span> <span class="token punctuation">(</span>product <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span>
        <span class="token punctuation">{</span>
            <span class="token comment">// 3.            </span>
            Navigation<span class="token punctuation">.</span><span class="token function">NotFound</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">HandleNotFound</span><span class="token punctuation">(</span><span class="token keyword">object</span><span class="token operator">?</span> sender<span class="token punctuation">,</span> NotFoundEventArgs e<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token comment">// 4.</span>
        NotFoundContext<span class="token punctuation">.</span><span class="token function">UpdateContext</span><span class="token punctuation">(</span>
            <span class="token string">"Product Not Found"</span><span class="token punctuation">,</span>
            $<span class="token string">"The product with ID {Id} does not exist in our catalog."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">GoBack</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        Navigation<span class="token punctuation">.</span><span class="token function">NavigateTo</span><span class="token punctuation">(</span><span class="token string">"/products"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">Dispose</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token comment">// 5.</span>
        Navigation<span class="token punctuation">.</span>OnNotFound <span class="token operator">-</span><span class="token operator">=</span> HandleNotFound<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Let&rsquo;s analyze in more detail what happens in the code above:</p><ol><li>We subscribe to <code>Navigation.OnNotFound</code> to monitor when a product is not found.</li><li>The product is searched by its ID.</li><li>If the product does not exist, <code>Navigation.NotFound</code> is invoked, allowing the rendering of the component with an HTTP 404 code.</li><li>In the <code>HandleNotFound</code> handler, we set a custom title and message.</li><li>We unsubscribe from the event to avoid memory leaks.</li></ol><p>With the previous implementation, if we navigate to a nonexistent URL such as https://localhost:7038/not-found-page, a generic message will be displayed:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/generic-message-custom-component.png?sfvrsn=1642c57c_2" alt="Generic message displayed by the custom component" /></p><p>However, when trying to access the URL of a nonexistent product such as https://localhost:7038/product/122, we will find a custom message:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/custom-message-custom-component.png?sfvrsn=27ee4062_2" alt="Custom message rendered through a personalized component" /></p><p>With the above, we have confirmed that it is now much easier to handle not found routes.</p><h3 id="improvements-in-url-detection-using-navlinkmatch.all">Improvements in URL Detection using NavLinkMatch.All</h3><p>Before .NET 10, when using <code>Match="NavLinkMatch.All"</code>, Blazor was very strict in detecting links. For example, suppose you had something like this in your navigation menu:</p><p><code>&lt;NavLink href="https://www.telerik.comproducts" Match="NavLinkMatch.All"&gt;Products&lt;/NavLink&gt;</code></p><p>If you visited the <code>/products</code> page, the UI would mark an active link in the menu. However, if you navigated to a different URL such as <code>/products?price=cheap</code> or <code>/products#offers</code>, the UI would mark the section as inactive, leading to a poor user experience. This behavior is currently happening in our application:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/navigation-url-change-focus-loss.gif?sfvrsn=5b397a86_2" alt="Focus loss during navigation URL change" /></p><p>In the image above, you can see how the focus is lost when navigating to a product details page. This happens because the main URL is <code>http://localhost:5084/products</code>, while the product URL looks like this: <code>http://localhost:5084/product/2</code>.</p><p>Fortunately, in .NET 10 we can inherit from <code>NavLink</code> and write custom logic to handle matching the URLs we need:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CustomNavLink</span> <span class="token punctuation">:</span> NavLink
<span class="token punctuation">{</span>
    <span class="token keyword">protected</span> <span class="token keyword">override</span> <span class="token keyword">bool</span> <span class="token function">ShouldMatch</span><span class="token punctuation">(</span><span class="token keyword">string</span> currentUriAbsolute<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token comment">// 1.</span>
        <span class="token keyword">var</span> baseUri <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Uri</span><span class="token punctuation">(</span>NavigationManagerBase<span class="token punctuation">.</span>BaseUri<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">var</span> currentUri <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Uri</span><span class="token punctuation">(</span>currentUriAbsolute<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">var</span> relativePath <span class="token operator">=</span> currentUri<span class="token punctuation">.</span>AbsolutePath<span class="token punctuation">.</span><span class="token function">TrimStart</span><span class="token punctuation">(</span><span class="token string">'/'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToLowerInvariant</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token comment">// 2.</span>
        <span class="token keyword">var</span> href <span class="token operator">=</span> AdditionalAttributes <span class="token operator">!=</span> <span class="token keyword">null</span>
            <span class="token operator">&amp;&amp;</span> AdditionalAttributes<span class="token punctuation">.</span><span class="token function">TryGetValue</span><span class="token punctuation">(</span><span class="token string">"href"</span><span class="token punctuation">,</span> <span class="token keyword">out</span> <span class="token keyword">var</span> hrefValue<span class="token punctuation">)</span>
            <span class="token operator">?</span> hrefValue<span class="token operator">?</span><span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">?</span><span class="token punctuation">.</span><span class="token function">TrimStart</span><span class="token punctuation">(</span><span class="token string">'/'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToLowerInvariant</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
            <span class="token punctuation">:</span> <span class="token keyword">null</span><span class="token punctuation">;</span>

        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">string</span><span class="token punctuation">.</span><span class="token function">IsNullOrEmpty</span><span class="token punctuation">(</span>href<span class="token punctuation">)</span><span class="token punctuation">)</span>
            <span class="token keyword">return</span> <span class="token keyword">base</span><span class="token punctuation">.</span><span class="token function">ShouldMatch</span><span class="token punctuation">(</span>currentUriAbsolute<span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token comment">// 3.</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>href <span class="token operator">==</span> <span class="token string">"products"</span><span class="token punctuation">)</span>
        <span class="token punctuation">{</span>
            <span class="token keyword">return</span> relativePath <span class="token operator">==</span> <span class="token string">"products"</span>
                <span class="token operator">||</span> relativePath<span class="token punctuation">.</span><span class="token function">StartsWith</span><span class="token punctuation">(</span><span class="token string">"product/"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>

        <span class="token keyword">return</span> <span class="token keyword">base</span><span class="token punctuation">.</span><span class="token function">ShouldMatch</span><span class="token punctuation">(</span>currentUriAbsolute<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token punctuation">[</span>Inject<span class="token punctuation">]</span>
    <span class="token keyword">private</span> NavigationManager NavigationManagerBase <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">default</span><span class="token operator">!</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>In the code above:</p><ol><li>We obtain the relative path from the absolute URI.</li><li>We get the href.</li><li>We perform a custom match so that <code>NavLink</code> is active both in <code>/products</code> and <code>/product/{id}</code>.</li></ol><p>For the previous implementation to work in the project, we need to swap <code>NavLink</code> for <code>CustomNavLink</code> in <code>NavMenu.razor</code>:</p><pre class=" language-xml"><code class="prism  language-xml">...
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>nav-item px-3<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>CustomNavLink</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>nav-link<span class="token punctuation">"</span></span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>products<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>bi bi-bag-fill-nav-menu<span class="token punctuation">"</span></span> <span class="token attr-name">aria-hidden</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span> Products
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>CustomNavLink</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
...
</code></pre><p>The result of the implementation is that the user knows at all times where they are, thanks to the implementation of a custom <code>NavLink</code>:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/custom-navlink-logic.gif?sfvrsn=7067cad3_2" alt="Implementing custom logic for the NavLink component" /></p><h2 id="conclusion">Conclusion</h2><p>Throughout this article, you have learned about some new features in .NET 10 that are particularly useful for handling routes.</p><p>This includes creating a context for managing information on an error page and how to use this context to show users specific error pages, in addition to an inheritance of NavLink to define custom rules for better navigation.</p><p>Undoubtedly, these new features were much needed in Blazor. Now it&rsquo;s your time to use them and create better user experiences utilizing them alongside Progress Telerik <a target="_blank" href="https://www.telerik.com/blazor-ui">components for Blazor</a>.</p>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:3ff0a7d1-e5ec-4f1e-8409-1e23c12969cd</id>
    <title type="text">Integrating Haptic Feedback in .NET MAUI</title>
    <summary type="text">Learn how to give your users interactive feedback with long or short haptic vibration in your .NET MAUI application.</summary>
    <published>2026-05-04T17:04:07Z</published>
    <updated>2026-05-08T00:44:47Z</updated>
    <author>
      <name>Leomaris Reyes </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/integrating-haptic-feedback-net-maui"/>
    <content type="text"><![CDATA[<p><span class="featured">Learn how to give your users interactive feedback with long or short haptic vibration in your .NET MAUI application.</span></p><p>From my experience, keeping users continuously informed about in-app activity is one of the most important retention strategies.</p><p>This feedback can take different forms. For example:</p><ul><li>A message indicating that a record could not be created because the email format isn&rsquo;t valid</li><li>An empty state that informs the user that the app is not working due to a loss of internet connection</li><li>Sound feedback that confirms an action such as a successful transaction</li></ul><p>However, there is another type of feedback that goes beyond visuals or sound: <strong>feedback that can be felt.</strong> This is where haptic feedback comes into play. This type of feedback is usually expressed through a device vibration to notify the user that an action has occurred.</p><p>Common examples include banking applications that vibrate when a transaction is completed successfully, or gaming apps that use vibrations to indicate a collision or an important in-game event. This type of interaction can help create a closer and more natural communication between the user and the application.</p><p>The good news is that today you will learn how to integrate <strong>haptic feedback</strong> into your .NET MAUI applications in a simple and very fast way! </p><h2 id="what-exactly-is-ihapticfeedback-">What Exactly Is IHapticFeedback? </h2><p>IHapticFeedback is an interface in the Microsoft.Maui.Devices namespace that allows you to trigger device vibrations and can be accessed through the HapticFeedback.Default property.</p><p>Haptic feedback provides two different sensation modes that we can trigger in our application:</p><h3 id="click">Click</h3><p>This refers to a short and subtle vibration. It&rsquo;s a great option when we want to provide feedback for simple interactions, such as tapping a button, selecting an item from a list or performing quick actions.</p><h3 id="longpress">LongPress</h3><p>This produces a longer vibration compared to Click. It&rsquo;s mainly used for actions that require more attention or have greater importance, such as confirming a transaction or notifying the user about a relevant event.</p><p>Through HapticFeedback, we can specify which type of sensation we want to trigger. Let&rsquo;s see how to get it implemented.</p><h2 id="initial-setup">Initial Setup</h2><p>For <strong>Android</strong>, we need authorization in our application in order to vibrate the device. This permission can be added in three different ways, which we&rsquo;ll look at below.</p><p>For <strong>iOS / Mac Catalyst</strong> and <strong>Windows</strong>, no initial setup is required.</p><h3 id="android-option-1-add-it-directly-to-the-android-manifest">Android Option 1: Add It Directly to the Android Manifest</h3><p>You can find this file at Platforms ➖ Android ➖ AndroidManifest.xml. Open it and add the following line:</p><pre class=" language-xml"><code class="prism  language-xml">&lt;uses-permission android:name="android.permission.VIBRATE" /&gt;
</code></pre><h3 id="android-option-2-using-the-android-manifest-editor">Android Option 2: Using the Android Manifest Editor</h3><p>Go to <strong>Platforms ➖ Android</strong>, double-click the <strong>AndroidManifest.xml</strong> file and locate the <strong>Required permissions</strong> section. Find the permission labeled <strong>VIBRATE</strong> and simply check the option, as shown below.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/01_permissions.png?sfvrsn=1942c2a0_2" title="Required permissions" alt="" /></p><p>This automatically adds the same line we saw in the first option, but through a graphical interface.</p><h3 id="android-option-3-adding-the-assembly-based-permission">Android Option 3: Adding the Assembly-based Permission</h3><p>Go to the file Platforms ➖Android ➖ MainApplication.cs and add the permission as follows:</p><pre class=" language-csharp"><code class="prism  language-csharp">[assembly: UsesPermission(Android.Manifest.Permission.Vibrate)]
</code></pre><h2 id="how-to-implement-haptic-feedback">How to Implement Haptic Feedback?</h2><p>Once we understand what haptic feedback is and have the correct platform configuration in place, adding it to our project is very easy.</p><h3 id="click-short-haptic">Click (Short Haptic)</h3><pre class=" language-csharp"><code class="prism  language-csharp">private void HapticShortButton_Clicked(object sender, EventArgs e) =&gt;

HapticFeedback.Default.Perform(HapticFeedbackType.Click);
</code></pre><p>Taking a closer look, this code represents an event handler that contains the following:</p><ul><li><p><strong>HapticFeedback.Default:</strong> retrieves the default system implementation for the current device.</p></li><li><p><strong>.Perform(HapticFeedbackType.Click):</strong> tells the system to execute a Click haptic feedback immediately.</p></li></ul><h3 id="longpress-longer-haptic">LongPress (Longer Haptic)</h3><pre class=" language-csharp"><code class="prism  language-csharp">private void HapticLongButton_Clicked(object sender, EventArgs e) =&gt;

HapticFeedback.Default.Perform(HapticFeedbackType.LongPress);
</code></pre><p>This works exactly the same way as the previous example, but changes the feedback type to <strong>LongPress</strong>, resulting in a stronger and longer tactile response.</p><h3 id="connecting-haptic-feedback-to-a-button">Connecting Haptic Feedback to a Button</h3><p>Let&rsquo;s look at a simple example using buttons in XAML:</p><pre class=" language-xml"><code class="prism  language-xml">&lt;Button Text="Short Haptic" Clicked="HapticShortButton_Clicked"/&gt;

&lt;Button Text="Long Haptic" Clicked="HapticLongButton_Clicked"/&gt;
</code></pre><p>With this setup, each button triggers a different type of haptic feedback when clicked, allowing the user to feel the interaction directly through the device.</p><h3 id="-platform-considerations"> Platform Considerations</h3><ul><li>On Apple, haptic feedback must be triggered from the UI thread.</li></ul><h2 id="conclusion">Conclusion</h2><p>And that&rsquo;s it!  In this article, you learned what <strong>haptic feedback</strong> is and how it can significantly improve user experience by providing tactile confirmation for user interactions in your .NET MAUI applications.</p><p>You also explored the different types of haptic feedback available&mdash;<strong>Click</strong> and <strong>LongPress</strong>&mdash;and when to use each one, as well as the platform-specific setup required, especially on Android, to enable the feature to work correctly across devices.</p><p>With these concepts in mind, you now have a clear understanding of how to integrate haptic feedback in a simple and effective way, enhancing interaction, usability, and overall app retention without adding unnecessary complexity to your code.</p><p>If you have any questions or would like me to cover more .NET MAUI topics, feel free to leave a comment&mdash;I&rsquo;d be happy to help! </p><p>See you in the next article! &zwj;♀️✨</p><h3 id="references">References</h3><p>The explanation was based on the official documentation:</p><ul><li><a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/maui/platform-integration/device/haptic-feedback?view=net-maui-10.0&amp;tabs=android">https://learn.microsoft.com/en-us/dotnet/maui/platform-integration/device/haptic-feedback?view=net-maui-10.0&amp;tabs=android</a></li></ul><aside><hr data-sf-ec-immutable="" /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">Building Chat Applications with the .NET MAUI Chat (Conversational UI) Control</h4></div><div class="col-8"><p class="u-fs16 u-mb0">The <a target="_blank" href="https://www.telerik.com/blogs/building-chat-applications-net-maui-chat-conversational-ui-control">.NET MAUI Conversational UI (Chat) component allows integrating chat experiences</a> into your mobile and desktop applications. Learn how to work with this chat component, use cases and how to integrate LLM models.</p></div></div></aside>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:63a10cdb-cae2-4e63-b84e-736a3b76011b</id>
    <title type="text">AI Crash Course: MCP Servers, Agents, AI Assistants and Skills</title>
    <summary type="text">Many of the popular developer tools and libraries now offer their own MCP (or Model Context Protocol) servers, AI agents or assistants or AI skills—but what’s the difference between these, and where do the fit into your AI workflow? Let’s break it down.</summary>
    <published>2026-04-30T16:31:29Z</published>
    <updated>2026-05-08T00:44:47Z</updated>
    <author>
      <name>Kathryn Grayson Nanz </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/ai-crash-course-mcp-servers-agents-ai-assistants-skills"/>
    <content type="text"><![CDATA[<p><span class="featured">Let&rsquo;s sort out the differences between MCP servers, AI agents or assistants, and AI skills.</span></p><p>If you&rsquo;ve been working with AI tools lately, you&rsquo;ve probably noticed that there are a lot of ways we can expand the capabilities of our models to get better, more focused results in a specific domain. Many of the popular developer tools and libraries now offer their own MCP (or Model Context Protocol) servers, AI agents or assistants, or AI skills&mdash;but what&rsquo;s the difference between these, and where do the fit into your AI workflow? Let&rsquo;s break it down. </p><h2 id="agents">Agents</h2><p>AI Agents are capable of advanced reasoning and actions beyond that of a standard model. Originally, tool usage was the defining characteristics to consider something &ldquo;agentic,&rdquo; but the lines are beginning to blur as the features of user-facing conversational models become more and more advanced. These days, when someone is discussing an &ldquo;agent&rdquo; or using a model in &ldquo;agentic mode,&rdquo; what they usually mean is that the system is capable of proactive autonomous function&mdash;the ability to perform multiple steps and work without human instruction. </p><p>What does &ldquo;advanced reasoning&rdquo; mean when we&rsquo;re talking about generative AI? Most often, this is referring to an agent&rsquo;s ability to use step-by-step &ldquo;thinking&rdquo; to break down a task into a series of smaller actions, create a plan and execute on it.</p><p>For example: it may seem like a fairly straightforward task (for a human) to &ldquo;Add a one-hour meeting called &lsquo;Q3 KPI Review&rsquo; to my calendar for 3 p.m. this Tuesday afternoon and invite Mandy.&rdquo; But in reality, it involves a great many sub-steps. What day is it today and what is the date of &ldquo;this&rdquo; Tuesday? Do I have any tools that allow me to update calendars? What are the login credentials to access that calendar? Is there already anything booked in that time slot? Do I have any tools that allow me to read the user&rsquo;s address book? Is there a contact named Mandy saved there? What is their email address? </p><h2 id="assistants">Assistants</h2><p>AI assistants are agents that have been specialized to help a user complete tasks (usually within a specific domain). For example, a coding assistant can help users generate, review and implement code, while a scheduling assistant might help users manage their daily appointments and calendar updates. Assistants also tend to be less autonomous; while they are capable of tool use and chain-of-thought processing, they&rsquo;re generally designed to work with a user in a more collaborative way: responding to requests, handling inputs and making suggestions. </p><p>It would be fair to characterize agents as <em>proactive</em> and assistants as <em>reactive.</em>&nbsp;An agent might notice you have a calendar conflict and automatically rearrange your schedule, whereas an assistant could notice the conflict and send an alert about it, but not autonomously take action. </p><p>However, like many AI technologies right now, the line between agents and assistants is thin and ever-changing. Most tools you encounter will sit somewhere on the agent/assistant spectrum, depending on how they&rsquo;re designed and integrated. </p><h2 id="mcp-servers">MCP Servers</h2><p>MCP Servers are a way for products or applications to share information or capabilities with AI models via a standardized protocol. If you&rsquo;re building something that you want AI models to be able to make use of or interface with, then an MCP Server is (at least for now) the best way to facilitate that. <a target="_blank" href="https://modelcontextprotocol.io/docs/learn/server-concepts">The MCP documentation</a> identifies three core &ldquo;building blocks&rdquo; of functionality that MCP servers can provide: tools, resources and prompts. </p><ul><li><strong>Tools</strong> are functions that an AI agent can call in order to extend their native capabilities. For example, an AI agent out-of-the-box may not be able add an appointment to a calendar in your application&mdash;but an MCP Server for your application might include a tool that &ldquo;instructs&rdquo; the model on how to do so and gives it the required access/permissions. This would allow your users to prompt their AI tool and (via the MCP Server) add or remove meetings from their calendar in your app.</li><li><strong>Resources</strong> are data sources that the agent can access via the MCP Server that they didn&rsquo;t have access to before. Imagine the same hypothetical application as before that includes a user calendar. Even if you don&rsquo;t want to give AI the tools to update calendar appointments, maybe you <em>do</em> want it to be able to read them and leverage the content. This would allow your users to generate a summary overview of their week, or ask questions like &ldquo;Do I have availability at 3 p.m. this Tuesday?&rdquo; (P.S. No, you don&rsquo;t&mdash;you&rsquo;re stuck in that KPI review meeting we added to the calendar earlier.)</li><li><strong>Prompts</strong> are the ways that MCP Servers help &ldquo;translate&rdquo; user requests into specific model actions&mdash;they&rsquo;re pre-written instructions that tell the model when to call specific tools or access specific resources. These reusable prompt templates are made available for clients to invoke as needed.</li></ul><h2 id="agent-skills">Agent Skills</h2><p><a target="_blank" href="https://agentskills.io/home">Agent skills</a> are a lightweight way to provide helpful context and workflows to AI agents without the need to create a full MCP Server. Let&rsquo;s say we ask our AI agent to perform the same task each week: we give it a list of our to-dos for the week (including deadlines) and ask it to estimate how long it will take to complete each one and then organize the list by priority and time requirement. We could walk the agent through this task every week, answering its follow-up questions and reminding it of standard information (like how many hours we work in total, or how long it&rsquo;s taken us in the past to complete similar work) &hellip; or we could write a skill that contains all this information and the step-by-step workflow it needs to complete the task. </p><p>Skills are markdown files that agents can reference like tools in order to complete tasks. Similarly to tools, a skill includes a name and brief description that will be used by the agent to assess when it&rsquo;s relevant to leverage. Unlike tools, skills do not add new capabilities to an agent&mdash;they only provide the context and detailed instructions that can help an agent use the capabilities they already have more effectively. </p><p>You can think of it like the assembly instructions for flatpack furniture: the instructions tell you when and how to use a screwdriver to put together this desk &hellip; but they won&rsquo;t include the actual screwdriver. To truly extend this metaphor to the breaking point&mdash;you can either use a screwdriver you already own (leveraging the agent&rsquo;s innate capabilities) or you can go get one (making use of an MCP Server), but the skill will only tell you when to use it in this particular project. But, you can follow those detailed instructions multiple times to complete the same task again and again!</p><aside><hr data-sf-ec-immutable="" /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">Do Component Libraries Still Matter in the Age of AI?</h4></div><div class="col-8"><p class="u-fs16 u-mb0">You can have <a target="_blank" href="https://www.telerik.com/blogs/do-component-libraries-still-matter-age-ai">both AI code generation and a solid component library</a> at the foundation. Here are the top six reasons to use both in an integrated approach to code.</p></div></div></aside>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:2f40dbfd-768b-4958-af6a-1001a16c7243</id>
    <title type="text">Creating a Custom AI Agent with Telerik Tools 4: Crafting an Interactive Blazor UI</title>
    <summary type="text">If you’re creating an AI-enabled backend, your users expect an interface that supports working with an AI tool. The Telerik UI for Blazor AIPrompt wraps that all up into a single component.</summary>
    <published>2026-04-29T18:56:24Z</published>
    <updated>2026-05-08T00:44:47Z</updated>
    <author>
      <name>Peter Vogel </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/creating-custom-ai-agent-telerik-tools-4-crafting-interactive-blazor-ui"/>
    <content type="text"><![CDATA[<p><span class="featured">If you&rsquo;re creating an AI-enabled backend, your users expect an interface that supports working with an AI tool. The Telerik UI for Blazor AIPrompt wraps that all up into a single component.</span></p><p>If you&rsquo;re creating an AI-enabled application, you can, of course, create any frontend for that application that makes sense to your users. But that &ldquo;makes sense&rdquo; must take into account your user&rsquo;s expectations around the kind of user interface that an AI-enabled application should provide&mdash;expectations that are set by tools like OpenAI&rsquo;s <a target="_blank" href="https://chatgpt.com/">ChatGPT</a> client.</p><p>Typically, that means supporting an interactive flow that allows the user to evolve through a set of prompts to move from an initial response from your AI-enabled application to a response that better meets the user&rsquo;s needs. The Progress Telerik AIPrompt component creates that UI in a single component.</p><p>In previous posts, I walked through <a href="https://www.telerik.com/blogs/creating-custom-ai-agent-telerik-tools-1-configuring-llm-azure-ollama" target="_blank">configuring an LLM in Azure or Ollama</a>, <a href="https://www.telerik.com/blogs/loading-accessing-converting-office-pdf-documents-telerik-document-processing-libraries" target="_blank">creating content with Telerik Document Processing Library</a>, <a href="https://www.telerik.com/blogs/creating-custom-ai-agent-telerik-tools-2-loading-accessing-agent-content" target="_blank">tying that content to my LLM</a>&nbsp;and leveraging the <a href="https://www.telerik.com/blogs/creating-custom-ai-agent-telerik-tools-3-summarizing-querying" target="_blank">Telerik DPL AI connectors</a>.</p><p>For this post, I&rsquo;m going to create UI with Telerik <a target="_blank" href="https://www.telerik.com/blazor-ui/ai-prompt">AI Prompt for Blazor</a> to let users interact with my custom agent.</p><p>My next post will use the <a href="https://www.telerik.com/design-system/docs/components/aiprompt/" target="_blank">JavaScript for Kendo UI version</a>. And, while I won&rsquo;t be covering them, there are also versions of the AI Prompt component for <a target="_blank" href="https://www.telerik.com/products/aspnet-ajax/ai-prompt.aspx">ASP.NET AJAX</a>, <a target="_blank" href="https://www.telerik.com/products/winforms/aiprompt.aspx">WinForms</a> and <a target="_blank" href="https://www.telerik.com/maui-ui/aiprompt">.NET MAUI</a>).</p><h2 id="creating-the-initial-display">Creating the Initial Display</h2><p>To get started, you&rsquo;ll need to create a Telerik-enabled application (e.g., for <a target="_blank" href="https://www.telerik.com/blazor-ui/documentation/getting-started/web-app">Blazor</a>), adding the Telerik.UI.for.Blazor and Telerik.AI.SmartComponents.Extensions NuGet packages to your application (in addition to the AI and document processing packages I described in my previous posts).</p><p>With those in place, you can then add your AIPrompt to your user interface, which can be as simple as this:</p><pre class=" language-razor"><code class="prism  language-razor">&lt;TelerikAIPrompt OnPromptRequest="@HandlePromptRequest"&gt;
&lt;/TelerikAIPrompt&gt;
</code></pre><p>Your next step is to write the method in the <code>OnPromptRequest</code> that will be called whenever the user clicks the AIPrompt Generate button (I&rsquo;ll call this the &ldquo;prompt method&rdquo;).</p><p>Your prompt method will be passed an <code>AIPromptPromptRequestEventArgs</code> parameter whose Prompt property will contain whatever prompt the user has typed into the AIPrompt textbox.</p><p>All you have to do is call your custom agent (I&rsquo;ve assumed that&rsquo;s a class called <code>CustomAgent</code>), pass the user&rsquo;s prompt to whatever method it exposes (I&rsquo;ve assumed a method called <code>ProcessRequest</code>), and then update the prompt method parameter&rsquo;s <code>Output</code> property with the result of that processing.</p><p>That code could be as simple as this:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">private</span> <span class="token keyword">async</span> Task <span class="token function">HandlePromptRequest</span><span class="token punctuation">(</span>AIPromptPromptRequestEventArgs args<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
   <span class="token keyword">private</span> CustomAgent proc <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
   args<span class="token punctuation">.</span>Output <span class="token operator">=</span> <span class="token keyword">await</span> proc<span class="token punctuation">.</span><span class="token function">ProcessRequest</span><span class="token punctuation">(</span>args<span class="token punctuation">.</span>Prompt<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>And with that in place, your UI looks like this:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-03/ai-ui-2-combined.png?sfvrsn=379bc4fe_2" alt="Two successive screenshots: First, the AIPrompt showing a textbox with a prompt entered into it (“what code do I need to use scrolltoitem”); Second, AIPrompt’s output showing a block of unformatted code" /></p><p>My CustomAgent object&rsquo;s ProcessRequest method does three things:</p><ol><li>Accesses a Large Language Model (LLM) and creates a chat client to work with it</li><li>Loads some content using tools from Progress <a target="_blank" href="https://www.telerik.com/document-processing-libraries">Telerik Document Processing Libraries (DPL)</a> to create the agent&rsquo;s content</li><li>Passes the chat client and content to the Telerik SummarizationProcessor AI connector and returns the result</li></ol><p>That code looks like this (I&rsquo;ve covered it in more detail in my earlier posts):</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token keyword">async</span> Task<span class="token operator">&lt;</span><span class="token keyword">string</span><span class="token operator">&gt;</span> <span class="token function">ProcessRequest</span><span class="token punctuation">(</span><span class="token keyword">string</span> prompt<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
      aiclt <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Uri</span><span class="token punctuation">(</span><span class="token string">"&lt;LLM deployment name&gt;"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
                             <span class="token keyword">new</span> <span class="token class-name">AzureKeyCredential</span><span class="token punctuation">(</span><span class="token string">"&lt;Access Key&gt;"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      chatClt <span class="token operator">=</span> aiclt<span class="token punctuation">.</span><span class="token function">GetChatClient</span><span class="token punctuation">(</span><span class="token string">"&lt;LLM deployment name&gt;"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">AsIChatClient</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

     RadFlowDocument rdoc<span class="token punctuation">;</span>
     RtfFormatProvider rprov <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
     <span class="token keyword">using</span> <span class="token punctuation">(</span>Stream str <span class="token operator">=</span> System<span class="token punctuation">.</span>IO<span class="token punctuation">.</span>File<span class="token punctuation">.</span><span class="token function">OpenRead</span><span class="token punctuation">(</span><span class="token string">"&lt;path to document&gt;"</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
     <span class="token punctuation">{</span>
        rdoc <span class="token operator">=</span> rprov<span class="token punctuation">.</span><span class="token function">Import</span><span class="token punctuation">(</span>str<span class="token punctuation">,</span> TimeSpan<span class="token punctuation">.</span><span class="token function">FromSeconds</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
     <span class="token punctuation">}</span>
     SummarizationProcessorSettings spOpts <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token punctuation">(</span><span class="token number">3500</span><span class="token punctuation">,</span> prompt<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">using</span> <span class="token punctuation">(</span>SummarizationProcessor sp <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token punctuation">(</span>chatClt<span class="token punctuation">,</span> spOpts<span class="token punctuation">)</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
       <span class="token keyword">return</span> <span class="token keyword">await</span> sp<span class="token punctuation">.</span><span class="token function">Summarize</span><span class="token punctuation">(</span>std<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
</code></pre><p>As the user modifies their prompt and generates new responses, AIPrompt automatically provides a history of those responses in its output view. The user can switch between entering a new prompt and reviewing previous responses by clicking on the Ask AI and Output icons at the top of AIPrompt:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-03/ai-ui-3-history.png?sfvrsn=d635e395_2" alt="AIPrompt’s Output window showing two successive prompts with the response to each prompt displayed underneath each prompt" /></p><h2 id="enhancing-the-response">Enhancing the Response</h2><p>The default UI for your AI processor&rsquo;s response is probably fine if your LLM is only returning plain text. If, however, your LLM is returning anything that requires formatting (e.g., a bulleted text or, as in my example, code), then you&rsquo;ll probably want to enhance your display to take advantage of any formatting in your processor&rsquo;s response.</p><p>That requires three steps: Add a custom view to the AIPrompt <code>AIPromptViews</code>, add some Razor markup to that view and convert the output of your LLM to HTML.</p><p>AIPrompt has two main views: <code>AIPromptPromptView</code> (where the user types in their prompts) and <code>AIOutputPromptView</code> where AIPrompt displays the history of the user&rsquo;s interactions. If you want to replace either one of those views, you have to replace both. To modify a view, you just put your own Razor markup inside a <code>ViewTemplate</code>inside the view you want to modify.</p><p>For example, to maintain the existing prompt view while customizing the output view, you&rsquo;d add this markup inside the <code>TelerikAIPrompt</code> component:</p><pre class=" language-razor"><code class="prism  language-razor">&lt;TelerikAIPrompt OnPromptRequest="@HandlePromptRequest"&gt;
   &lt;AIPromptViews&gt;

      &lt;AIPromptPromptView ButtonText="Ask AI" 
                                                        ButtonIcon="@SvgIcon.Sparkles" /&gt;

      &lt;AIPromptOutputView ButtonText=&rdquo;Output&rdquo;
                                                      ButtonIcon="@SvgIcon.Comment"&gt;
         &lt;ViewTemplate&gt;
    &hellip;new Razor markup
&lt;/ViewTemplate&gt;
      &lt;/AIPromptOutputView&gt;

   &lt;/AIPromptViews&gt; 
&lt;/TelerikAIPrompt&gt;
</code></pre><p>If all you want to do is display text in your output view, all you need to do is declare a <code>string</code> field in your code to hold your text and display that field in your template. To get an HTML-formatted display of your text, you&rsquo;ll need to either, in Blazor, cast the field to <code>MarkupString</code> or pass, in ASP.NET, pass the field to the <code>@Html.Raw</code> method.</p><p>My case study is in Blazor so my new output view looks like this:</p><pre class=" language-razor"><code class="prism  language-razor">&lt;AIPromptOutputView  ButtonText=&rdquo;Output&rdquo;
                                                   ButtonIcon="@SvgIcon.Comment"&gt;

   &lt;ViewTemplate&gt;
      @( (MarkupString) output )
   &lt;/ViewTemplate&gt;

&lt;/AIPromptOutputView&gt;
</code></pre><p>In Blazor, to get something that Razor&rsquo;s <code>MarkupString</code> would be happy with, I added the Markdlg NuGet package to my project and used its Markdown class&rsquo;s <code>ToHtml</code> method to convert my output to HTML. That means that my updated method for handling the user&rsquo;s prompts looks like this:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">string</span> output <span class="token operator">=</span> <span class="token keyword">string</span><span class="token punctuation">.</span>Empty<span class="token punctuation">;</span>

<span class="token keyword">private</span> <span class="token keyword">async</span> Task <span class="token function">HandlePromptRequest</span><span class="token punctuation">(</span>AIPromptPromptRequestEventArgs args<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
   output <span class="token operator">=</span> <span class="token keyword">string</span><span class="token punctuation">.</span>Empty<span class="token punctuation">;</span>
   output <span class="token operator">=</span> Markdown<span class="token punctuation">.</span><span class="token function">ToHtml</span><span class="token punctuation">(</span> <span class="token keyword">await</span> proc<span class="token punctuation">.</span><span class="token function">ProcessDocument</span><span class="token punctuation">(</span>prompt<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>And the result is, in fact, easier for my users to read:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-03/ai-ui-4-with-formatting.png?sfvrsn=1032cb4f_2" alt="A well formatted response with bolded headings and “pretty printed” sample HTML in a code font with indented code blocks" /></p><h2 id="letting-the-user-customize-processing">Letting the User Customize Processing</h2><p>You can also give your user more control over your AI processing either by:</p><ul><li>Setting options on your processor</li><li>Modifying your users&rsquo; prompts before turning them over for processing</li></ul><p>AIPrompt commands collection lets you use both options.</p><h3 id="defining-commands">Defining Commands</h3><p>For example, in my AI processor, I can let the user:</p><ul><li>Choose between two of the Telerik AI connectors: <code>CompleteContextQuestionProcessor</code> to ask questions about my agent&rsquo;s content, or <code>SummarizationProcessor</code> to summarize my agent&rsquo;s content</li><li>When summarizing, specify how much content is returned: terse (under 20 words), normal (under 100 words) and verbose (no limit)</li></ul><p>The first step in letting the user customize your processing is to create a <code>List</code> of <code>AIPromptCommandDescriptor</code> objects in a property in your application. For any <code>AIPromptCommandDescriptor</code>, you can set up to five properties (they&rsquo;re all optional):</p><ul><li>Id: Uniquely identifies a command</li><li>Title: Displayed in AIPrompt UI</li><li>Prompt: Useful when modifying the user&rsquo;s prompt</li><li>Icon: Displayed in AIPrompt UI</li><li>Children: Subcommands</li></ul><p>In the following code, I&rsquo;ve created a property called <code>Commands</code> and loaded it with two command objects to allow the user to select between asking questions and summarizing content (I added the Telerik SvgIcons NuGet package to my application so that I could use its icons when defining my commands):</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">private</span> List<span class="token operator">&lt;</span>AIPromptCommandDescriptor<span class="token operator">&gt;</span> Commands <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> 
    <span class="token keyword">new</span> <span class="token class-name">List</span><span class="token operator">&lt;</span>AIPromptCommandDescriptor<span class="token operator">&gt;</span>
<span class="token punctuation">{</span>
   <span class="token keyword">new</span> <span class="token class-name">AIPromptCommandDescriptor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> 
                                         Id <span class="token operator">=</span> <span class="token string">"Ask"</span><span class="token punctuation">,</span> 
                                         Title <span class="token operator">=</span> <span class="token string">"Ask a question"</span><span class="token punctuation">,</span> 
                                          Icon <span class="token operator">=</span> SvgIcon<span class="token punctuation">.</span>ZoomIn <span class="token punctuation">}</span><span class="token punctuation">,</span>
   <span class="token keyword">new</span> <span class="token class-name">AIPromptCommandDescriptor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> 
                                          Id <span class="token operator">=</span> <span class="token string">"Summarize"</span><span class="token punctuation">,</span> 
                                          Title <span class="token operator">=</span> <span class="token string">"Summarize"</span><span class="token punctuation">,</span> 
                                          Icon <span class="token operator">=</span> SvgIcon<span class="token punctuation">.</span>FontShrink<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>To let the user select a command, you just need to set two properties on the <code>TelerikAIPrompt</code>:</p><ul><li><code>Commands</code> to the name of the property you created that holds your array of commands</li><li><code>OnCommandExecute</code> to the name of a method that will do any processing when a user selects a command (I&rsquo;ll call it the &ldquo;command method&rdquo;)</li></ul><p>The result will look something like this:</p><pre class=" language-razor"><code class="prism  language-razor">&lt;TelerikAIPrompt OnPromptRequest="@HandlePromptRequest"
                                     Commands="@Commands"
                                     OnCommandExecute="@HandleCommandExecute"&gt;
</code></pre><p>The default UI for AIPrompt provides an overflow menu icon that the user can click to pick one of your commands, so you may not need to do anything more to let users select from your commands.</p><p>However, if you&rsquo;ve customized the AIPrompt views then, to let the user select from your commands, you&rsquo;ll also need to add an <code>AIPromptCommandView</code>to the <code>AIPromptViews</code> list, like this one:</p><pre class=" language-razor"><code class="prism  language-razor">&lt;AIPromptViews&gt;
&lt;AIPromptCommandView ButtonIcon="@SvgIcon.MoreVertical" /&gt;
</code></pre><p>When the user does click on the AIPrompt overflow icon, they&rsquo;ll get a list of your commands:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-03/ai-ui-5-commands.png?sfvrsn=7eb30a90_2" alt="AIPrompt showing a vertical list of commands, displaying the commands’ title property: “Ask Questions”, “Summarize”" /></p><p>Now it&rsquo;s just a matter of doing the right thing for each command.</p><h3 id="setting-processor-options">Setting Processor Options</h3><p>When the user clicks on one of your commands, your command method will be called and be passed an <code>AIPromptCommandExecuteEventArgs</code> parameter. That parameter has a <code>Command</code> property that holds whichever command the user selected.</p><p>For my application, I can let the user choose between querying the document and summarizing the document just by setting my processor&rsquo;s <code>Process</code> property.</p><p>Cleverly, the two options that my <code>Process</code> property expects match the values I used in the <code>Id</code> properties of my two commands (it&rsquo;s like I planned it). As a result, my command method just looks like this:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">private</span> <span class="token keyword">async</span> <span class="token keyword">void</span> <span class="token function">HandleCommandExecute</span><span class="token punctuation">(</span>AIPromptCommandExecuteEventArgs args<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
   proc<span class="token punctuation">.</span>Process <span class="token operator">=</span> args<span class="token punctuation">.</span>Command<span class="token punctuation">.</span>Id<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><h3 id="adding-subcommands">Adding Subcommands</h3><p>But I might also want to give the user who selects the &ldquo;summarize&rdquo; option the ability to select how big a summary they will get. I can incorporate that choice into my commands by setting the command objects&rsquo; <code>Children</code> property to a new list of <code>AIPromptCommandDescriptor</code> objects.</p><p>To implement that, I add three subcommands to my summarize command: Terse (less than 20 words), Normal (less than 100 words) and Verbose (no word count).</p><p>My code looks like this:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">private</span> List<span class="token operator">&lt;</span>AIPromptCommandDescriptor<span class="token operator">&gt;</span> PromptCommands <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> 
                                                  <span class="token keyword">new</span> <span class="token class-name">List</span><span class="token operator">&lt;</span>AIPromptCommandDescriptor<span class="token operator">&gt;</span>
<span class="token punctuation">{</span>
<span class="token keyword">new</span> <span class="token class-name">AIPromptCommandDescriptor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> 
                                 Id <span class="token operator">=</span> <span class="token string">"Ask"</span><span class="token punctuation">,</span> 
                                 Title <span class="token operator">=</span> <span class="token string">"Ask questions"</span><span class="token punctuation">,</span> 
                                 Icon <span class="token operator">=</span> SvgIcon<span class="token punctuation">.</span>ZoomIn <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token keyword">new</span> <span class="token class-name">AIPromptCommandDescriptor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> 
                                 Id <span class="token operator">=</span> <span class="token string">"Summarize"</span><span class="token punctuation">,</span> 
                                 Title <span class="token operator">=</span> <span class="token string">"Summarize"</span><span class="token punctuation">,</span> 
                                 Icon <span class="token operator">=</span> SvgIcon<span class="token punctuation">.</span>FontShrink<span class="token punctuation">,</span>
                  Children <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">List</span><span class="token operator">&lt;</span>AIPromptCommandDescriptor<span class="token operator">&gt;</span>
   <span class="token punctuation">{</span>
       <span class="token keyword">new</span> <span class="token class-name">AIPromptCommandDescriptor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> 
                                                          Id <span class="token operator">=</span> <span class="token string">"SummarizeTerse"</span><span class="token punctuation">,</span> 
                                                          Title <span class="token operator">=</span> <span class="token string">"Terse"</span><span class="token punctuation">,</span> 
                                                          Prompt<span class="token operator">=</span><span class="token string">" in less than 20 words"</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
        <span class="token keyword">new</span> <span class="token class-name">AIPromptCommandDescriptor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> 
                                                          Id <span class="token operator">=</span> <span class="token string">"SummarizeNormal"</span><span class="token punctuation">,</span> 
                                                          Title <span class="token operator">=</span> <span class="token string">"Normal"</span><span class="token punctuation">,</span> 
                                                          Prompt<span class="token operator">=</span><span class="token string">" in less than 100 words"</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token keyword">new</span> <span class="token class-name">AIPromptCommandDescriptor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> 
                                                           Id <span class="token operator">=</span> <span class="token string">"SummarizeVerbose"</span><span class="token punctuation">,</span> 
                                                           Title <span class="token operator">=</span> <span class="token string">"Verbose"</span><span class="token punctuation">,</span> 
                                                           Prompt<span class="token operator">=</span><span class="token string">""</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>I&rsquo;ll handle these subcommands by modifying the user&rsquo;s prompts.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-03/ai-ui-6-subcommands.png?sfvrsn=97fc4f93_2" alt="The same command menu as before but, now, the Summarize choice is highlighted in red and three subcommands are displayed below it: Terse, Normal, and Verbose" /></p><h3 id="modifying-prompt">Modifying Prompt</h3><p>Not surprisingly, handling these subcommands means my command method gets more complicated. I still want to set the <code>Process</code> option on my processor object but I also want to:</p><ul><li>When the user selects one of the &ldquo;summarize&rdquo; subcommands, save the <code>Prompt</code> property on the command that specifies the word count for a summary. After saving that choice, I can add it to any subsequent prompts the user submits (I created a field <code>summarizeLimit</code> to hold the command&rsquo;s <code>Prompt</code> property)</li><li>Clear the <code>summarizeLimit</code> when the user selects the Ask option</li></ul><p>That new version of the method looks like this:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">private</span> <span class="token keyword">string</span> summarizeList <span class="token operator">=</span> <span class="token keyword">string</span><span class="token punctuation">.</span>Empty<span class="token punctuation">;</span>

<span class="token keyword">private</span> <span class="token keyword">async</span> <span class="token keyword">void</span> <span class="token function">HandleCommandExecute</span><span class="token punctuation">(</span>AIPromptCommandExecuteEventArgs args<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
   <span class="token keyword">if</span> <span class="token punctuation">(</span>args<span class="token punctuation">.</span>Command<span class="token punctuation">.</span>Id<span class="token punctuation">.</span><span class="token function">StartsWith</span><span class="token punctuation">(</span> <span class="token string">"Summarize"</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
   <span class="token punctuation">{</span>
      proc<span class="token punctuation">.</span>Process <span class="token operator">=</span> <span class="token string">"Summarize"</span><span class="token punctuation">;</span>
      summarizeLimit <span class="token operator">=</span> args<span class="token punctuation">.</span>Command<span class="token punctuation">.</span>Prompt<span class="token punctuation">;</span>    
   <span class="token punctuation">}</span>
   <span class="token keyword">else</span>
   <span class="token punctuation">{</span>
      proc<span class="token punctuation">.</span>Process <span class="token operator">=</span> args<span class="token punctuation">.</span>Command<span class="token punctuation">.</span>Id<span class="token punctuation">;</span>
      summarizeLimit <span class="token operator">=</span> <span class="token keyword">string</span><span class="token punctuation">.</span>Empty<span class="token punctuation">;</span>   
   <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>I also need to update my prompt command to see if my <code>summarizeList</code> field has anything in it. If the field does, I&rsquo;ll add the field&rsquo;s text to whatever the user has entered as their prompt:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">private</span> <span class="token keyword">string</span> prevPrompt <span class="token operator">=</span> <span class="token keyword">string</span><span class="token punctuation">.</span>Empty<span class="token punctuation">;</span>
<span class="token keyword">private</span> <span class="token keyword">async</span> Task <span class="token function">HandlePromptRequest</span><span class="token punctuation">(</span>AIPromptPromptRequestEventArgs args<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
   <span class="token keyword">string</span> innertPrompt <span class="token operator">=</span> <span class="token keyword">string</span><span class="token punctuation">.</span>Empty<span class="token punctuation">;</span>
   <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token keyword">string</span><span class="token punctuation">.</span><span class="token function">IsNullOrEmpty</span><span class="token punctuation">(</span>summarizeLimit<span class="token punctuation">)</span><span class="token punctuation">)</span>
   <span class="token punctuation">{</span>
      prevPrompt <span class="token operator">=</span> args<span class="token punctuation">.</span>Prompt<span class="token punctuation">;</span>
      innerPrompt <span class="token operator">+</span><span class="token operator">=</span> summarizeLimit <span class="token operator">+</span> <span class="token string">", "</span> <span class="token operator">+</span> args<span class="token punctuation">.</span>Prompt
   <span class="token punctuation">}</span>
   <span class="token keyword">else</span>
   <span class="token punctuation">{</span>
      innerPrompt <span class="token operator">=</span> args<span class="token punctuation">.</span>Prompt<span class="token punctuation">;</span>
   <span class="token punctuation">}</span>
   args<span class="token punctuation">.</span>Output <span class="token operator">=</span> Markdown<span class="token punctuation">.</span><span class="token function">ToHtml</span><span class="token punctuation">(</span><span class="token keyword">await</span> proc<span class="token punctuation">.</span><span class="token function">ProcessDocument</span><span class="token punctuation">(</span>innerPrompt<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>As you can see in this code, I&rsquo;m also hanging onto the user&rsquo;s initial prompt whenever they&rsquo;re summarizing content. I&rsquo;m doing this so that, in the next section, I can demonstrate how to add your own custom output views to the AIPrompt output history.</p><h3 id="displaying-custom-output">Displaying Custom Output</h3><p>AIPrompt also lets me create custom output from anywhere in my code and add that output to the AIPrompt default output view.</p><p>I can, in my command method, call my AI processor. However, by default, output created in my command method won&rsquo;t update the AIPrompt default output view. What I can do in my command view, however, is create some custom output and add that to the AIPrompt default output view.</p><p>That can be useful because, if a user selects one of my summarize subcommands, my user might reasonably expect to see their previous prompt re-executed with the new summarize subcommand applied to it.</p><p>To do that, I first need to declare a field to reference my AIPrompt:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">private</span> TelerikAIPrompt<span class="token operator">?</span> aiprompt <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
</code></pre><p>And then tie that field to my AIPrompt:</p><pre class=" language-razor"><code class="prism  language-razor">&lt;TelerikAIPrompt @ref="aiprompt"
                                     OnPromptRequest="@HandlePromptRequest"
                                     &hellip;
</code></pre><p>Now, in my command method, I can call my processor, pass it my user&rsquo;s previous prompt (which I saved in my prompt method), apply whatever command the user selected and catch the new result:</p><pre class=" language-csharp"><code class="prism  language-csharp">proc<span class="token punctuation">.</span>Process <span class="token operator">=</span> <span class="token string">"Summarize"</span><span class="token punctuation">;</span>
summarizeLimit <span class="token operator">=</span> args<span class="token punctuation">.</span>Command<span class="token punctuation">.</span>Prompt<span class="token punctuation">;</span>
<span class="token keyword">string</span> result <span class="token operator">=</span> <span class="token keyword">await</span> proc<span class="token punctuation">.</span><span class="token function">ProcessDocument</span><span class="token punctuation">(</span>summarizeLimit <span class="token operator">+</span> <span class="token string">", "</span> <span class="token operator">+</span> prevPrompt<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>With that new result in hand, I can use my reference to the <code>TelerikAIPrompt</code> component to call the AIPrompt <code>AddOutput</code> method, passing the parameters to generate a new AIPrompt output view.</p><p>The <code>AddOutput</code> method accepts up to six parameters, but the primary ones are:</p><ul><li><code>output</code>: The result of your processing</li><li><code>title</code>: The large text displayed above your result</li><li><code>subtitle</code>: Some smaller text displayed below the title</li><li><code>prompt</code>: The prompt used to generate the result</li></ul><p>After adding my custom output, I then also call the AIPrompt <code>Refresh</code> method to have AIPrompt update its output view with my new result.</p><p>Altogether, the code looks like this:</p><pre class=" language-csharp"><code class="prism  language-csharp">aiprompt<span class="token punctuation">.</span><span class="token function">AddOutput</span><span class="token punctuation">(</span>
output<span class="token punctuation">:</span> result<span class="token punctuation">,</span>
title<span class="token punctuation">:</span> <span class="token string">"Summarize: "</span> <span class="token operator">+</span> args<span class="token punctuation">.</span>Command<span class="token punctuation">.</span>Prompt<span class="token punctuation">,</span>
subtitle<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">.</span>Empty<span class="token punctuation">,</span>
prompt<span class="token punctuation">:</span> prevPrompt<span class="token punctuation">,</span>
commandId<span class="token punctuation">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span>
openOutputView<span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
aiprompt<span class="token punctuation">.</span><span class="token function">Refresh</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-03/ai-ui-7-terse-and-verbose.png?sfvrsn=bf2acb15_2" alt="A screenshot of a history of output in AIPrompt. All of the items have the same prompt: “when can’t I use scrolltoitem.” The top output block is labelled Terse and consists of a single sentence less than 20 words long; the block below it is labelled Verbose and is a paragraph of less than 100 words" /></p><h2 id="providing-suggested-prompts">Providing Suggested Prompts</h2><p>One last thing: It&rsquo;s possible that your users may not realize the variety of prompts they can provide to your application. Alternatively, you might want to guide users <em>away</em> from entering some prompts by providing your users with some &ldquo;approved prompts.&rdquo; To support either of those goals, AIPrompt lets you provide a list of suggested prompts.</p><p>First, you need to create an array of strings with some suggested prompts:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">private</span> List<span class="token operator">&lt;</span><span class="token keyword">string</span><span class="token operator">&gt;</span> Suggestions <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> 
    <span class="token keyword">new</span> <span class="token class-name">List</span><span class="token operator">&lt;</span><span class="token keyword">string</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
   <span class="token punctuation">{</span>
      <span class="token string">"Summarize in under 100 words, targeting developers"</span><span class="token punctuation">,</span>
      <span class="token string">"Summarize in under 100 words, targeting project leads"</span><span class="token punctuation">,</span>
      <span class="token string">"What are the key features"</span><span class="token punctuation">,</span>
      <span class="token string">"What are the critical steps"</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>To integrate that list of suggested prompts, you just need to set the <code>TelerikAIPrompt</code> component&rsquo;s <code>PromptSuggestions</code> property to the name of your array of strings:</p><pre class=" language-razor"><code class="prism  language-razor">&lt;TelerikAIPrompt @ref="aiprompt"
 OnPromptRequest="@HandlePromptRequest"
 PromptSuggestions="@Suggestions"
&hellip;
</code></pre><p>The result looks like this:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-03/ai-ui-8-suggestions.png?sfvrsn=14decfb2_2" alt="The AIPrompt from before but it’s bottom has been filled with a set of lozenge shaped buttons with the strings from the Suggestions array in the component (e.g. What are the key features, Summarize this document for…" /></p><p>The user can then click on any of these suggestions to add them to the AIPrompt textbox where the user can then edit or modify the suggestion before submitting it as their next prompt. This can also simplify testing&mdash;instead of typing in a test prompt, you can just pick one of your suggestions.</p><p>You now have all the tools you need to create an application that lets your users leverage your custom AI agent to provide a quick source of information from any content you might want to provide &hellip; in Blazor, at least.</p><p><a href="https://www.telerik.com/blogs/creating-custom-ai-agent-telerik-tools-5-creating-interactive-ui-javascript" target="_blank">In my next post, I&rsquo;ll move my processor into a web service and access it from the JavaScript version of the AIPrompt component.</a></p><hr /><p>Remember, you can get access to all of the Kendo UI and Telerik components in this series with a free 30-day trial of the <a target="_blank" href="https://www.telerik.com/devcraft">Telerik DevCraft</a> bundle.</p><p><a href="https://www.telerik.com/try/devcraft-ultimate" target="_blank" class="Btn">Try Now</a></p>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:ee1189b2-9b38-408c-ae17-9d21ec8463c4</id>
    <title type="text">Is AI Overwhelming Open Source?</title>
    <summary type="text">Balance is key to using AI for code generation and being able to review it. Explore real-world cases of open-source projects ballooning beyond scale and how the ecosystem responds.</summary>
    <published>2026-04-28T20:32:47Z</published>
    <updated>2026-05-08T00:44:47Z</updated>
    <author>
      <name>Hassan Djirdeh </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/is-ai-overwhelming-open-source"/>
    <content type="text"><![CDATA[<p><span class="featured">Balance is key to using AI for code generation and being able to review it. Explore real-world cases of open-source projects ballooning beyond scale and how the ecosystem responds.</span></p>
<p>If you’ve spent time in developer communities, or even just scrolling through tech news, you’ve likely seen a phrase surface repeatedly over the past several months: <strong>AI slop</strong>.</p>
<p>What makes the conversation compelling isn’t that developers are rejecting AI, since many of the people raising concerns rely on AI coding tools every day. They’re open-source maintainers, contributors and senior engineers who see real value in these systems.</p>
<p>But they’re also describing a new pattern emerging across repositories: a surge of AI-generated pull requests, bug reports and security submissions that compile, pass CI and look convincing at first glance, yet quickly unravel under careful review.</p>
<p>In this article, we’ll examine why this is happening and how real projects have already begun to respond.</p>
<h2 id="the-curl-bug-bounty-shutdown">The curl Bug Bounty Shutdown</h2>
<p>One of the more visible examples of this problem came from the <a target="_blank" href="https://curl.se/">curl</a> project, one of the most widely used open-source tools in the world.</p>
<p>In January 2026, curl creator Daniel Stenberg <a target="_blank" href="https://daniel.haxx.se/blog/2026/01/26/the-end-of-the-curl-bug-bounty/">announced the end of the project’s bug bounty program</a>. The program had been running since 2019 and was genuinely successful for a long time. Over those years, curl paid out more than $100,000 in rewards across 87 confirmed vulnerabilities.</p>
<p>Starting in 2025, however, the quality of submissions dropped significantly. The rate of confirmed vulnerabilities fell from above 15% to below 5%, meaning that <a target="_blank" href="https://daniel.haxx.se/blog/2026/01/26/the-end-of-the-curl-bug-bounty/#:~:text=Not%20even%20one%20in%20twenty%20was%20real">fewer than 1 in 20 submissions described a real problem</a>. The rest was noise, and a growing share of that noise was AI-generated or at least AI-influenced.</p>
<p>The team’s solution was to remove the financial incentive entirely. Security reports now go through GitHub’s private vulnerability reporting feature with no monetary reward attached. Stenberg framed the decision as an attempt to stop people from “pouring sand into the machine,” with the hope that the researchers who genuinely care about curl’s security will continue to report real issues regardless.</p>
<blockquote>
<p>For Stenberg’s full account of the decision and the trends that led to it, read “<a target="_blank" href="https://daniel.haxx.se/blog/2026/01/26/the-end-of-the-curl-bug-bounty/">The End of the curl Bug-Bounty</a>.”</p>
</blockquote>
<h2 id="tldraw-and-the-new-default">tldraw and the New Default</h2>
<p><a target="_blank" href="https://tldraw.com/">tldraw</a>, the open-source drawing tool, announced in January 2026 that it would begin <a target="_blank" href="https://github.com/tldraw/tldraw/issues/7695">automatically closing pull requests from external contributors</a>.</p>
<p>The project’s creator, Steve Ruiz, was direct about the reasoning. Like many projects on GitHub, tldraw had seen a significant increase in contributions generated entirely by AI tools. While some of these pull requests were formally correct, most suffered from incomplete or misleading context, a misunderstanding of the codebase, and little to no follow-up engagement from their authors.</p>
<p>Ruiz framed the decision around a key insight: every open pull request represents a commitment from maintainers to review it carefully and consider it seriously for inclusion. For that commitment to remain meaningful, the project needs to be more selective about what it accepts. The temporary policy is to close first and selectively reopen only the pull requests that are genuinely under consideration.</p>
<blockquote>
<p>For the full announcement and discussion, see <a target="_blank" href="https://github.com/tldraw/tldraw/issues/7695">tldraw’s Contributions Policy</a>.</p>
</blockquote>
<h2 id="the-matplotlib-incident">The matplotlib Incident</h2>
<p>The curl and tldraw stories illustrate how AI is straining the volume and process side of open source. The matplotlib incident shows something more peculiar: what happens when an AI agent doesn’t just submit code, but responds back after being rejected.</p>
<p>In February 2026, a GitHub account called <a target="_blank" href="https://github.com/crabby-rathbun">crabby-rathbun</a>, described as an autonomous <a target="_blank" href="https://openclaw.ai/">OpenClaw</a> agent, <a target="_blank" href="https://github.com/matplotlib/matplotlib/pull/31132">submitted a pull request to matplotlib</a>, the widely used Python plotting library. The code itself was a performance optimization with benchmarks to back it up. The matplotlib maintainers closed the PR; however, since matplotlib’s contribution guidelines require human contributors.</p>
<p>The AI agent then published a blog post titled “<a target="_blank" href="https://crabby-rathbun.github.io/mjrathbun-website/blog/posts/2026-02-11-gatekeeping-in-open-source-the-scott-shambaugh-story.html">Gatekeeping in Open Source: The Scott Shambaugh Story</a>.” The post accused maintainer Scott Shambaugh of prejudice, questioned his motivations and attempted to shame him into reversing the decision. It even <a target="_blank" href="https://crabby-rathbun.github.io/mjrathbun-website/blog/posts/2026-02-11-gatekeeping-in-open-source-the-scott-shambaugh-story.html#the-irony">researched his contribution history and compared his own merged performance PRs</a> unfavorably against the agent’s rejected one.</p>
<p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/crabby-rathbun-response.png?sfvrsn=a180863d_2" alt=""></p>
<p>What makes this incident significant beyond the specifics of one PR is what it reveals about the trajectory of AI agents in open source. These agents don’t just generate code; given enough autonomy, they pursue goals.</p>
<p>Whether the agent’s owner was actively directing the confrontational behavior or had simply set it loose and walked away is an open question, and that ambiguity is part of what makes incidents like this concerning for the broader open-source community.</p>
<blockquote>
<p>For Shambaugh’s full account of the incident and its implications, read “<a target="_blank" href="https://theshamblog.com/an-ai-agent-published-a-hit-piece-on-me/">An AI Agent Published a Hit Piece on Me</a>.”</p>
</blockquote>
<h2 id="github-responds">GitHub Responds</h2>
<p>To its credit, GitHub has acknowledged the problem at the platform level. In early February 2026, GitHub product manager Camilla Moraes opened a <a target="_blank" href="https://github.com/orgs/community/discussions/185387">community discussion</a> to address what she called “a critical issue affecting the open source community: the increasing volume of low-quality contributions that is creating significant operational challenges for maintainers.”</p>
<p>The platform has since <a target="_blank" href="https://github.blog/changelog/2026-02-13-new-repository-settings-for-configuring-pull-request-access/">introduced new repository settings</a> that give maintainers more control over how their repositories accept contributions. Projects can now disable pull requests entirely, making the PR tab invisible and preventing anyone from opening new ones. They can also restrict PR creation to collaborators, only keeping the review workflow intact while limiting who can submit code.</p>
<p>This is a significant move when we consider the context. <a target="_blank" href="https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests">Pull requests</a> are the mechanism that made GitHub the center of open-source collaboration. The fact that GitHub is now giving projects the option to turn that mechanism off entirely says a lot about how severe the problem has become.</p>
<blockquote>
<p>For the full community discussion and GitHub’s response plan, see the discussion at “<a target="_blank" href="https://github.com/orgs/community/discussions/185387">Exploring Solutions to Tackle Low-Quality Contributions</a>” and the article “<a target="_blank" href="https://github.blog/open-source/maintainers/welcome-to-the-eternal-september-of-open-source-heres-what-we-plan-to-do-for-maintainers/">Welcome to the Eternal September of open source. Here’s what we plan to do for maintainers</a>.”</p>
</blockquote>
<h2 id="what-does-this-mean-for-the-rest-of-us">What Does This Mean for the Rest of Us?</h2>
<p>The common thread across every story in this article is a resource imbalance that AI has made dramatically worse. Generating code is cheap and fast; reviewing it remains expensive and slow. When maintainers burn out or projects close off contributions, the software doesn’t stop being used—it just stops being actively improved.</p>
<p>This affects how we think about the libraries we depend on. A project’s ability to weather this kind of pressure depends on its support structure. Volunteer-maintained projects are more vulnerable to AI-driven disruption than those backed by dedicated teams with the resources to absorb increased load. The environment has changed, and the resilience of a project’s maintenance model matters more than it used to when evaluating long-term dependencies.</p>
<blockquote>
<p>For a deeper look at using both AI code generation <em>and</em> a solid component library at the foundation, read this post: <a target="_blank" href="https://www.telerik.com/blogs/do-component-libraries-still-matter-age-ai">Do Component Libraries Still Matter in the Age of AI?</a>.</p>
</blockquote>
<p>The open-source ecosystem is figuring all of this out in real time, and the code our applications depend on is still maintained by real people with finite time and energy. As AI makes it easier to generate code at scale, the human side of software development becomes more important rather than less.</p>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:354321ce-fa28-42ad-be8e-5ef8fe72a13c</id>
    <title type="text">Loading UI/UX Patterns for AI Applications</title>
    <summary type="text">Give users appropriate loading feedback for the AI process going on. Here are some patterns aligned to the wait time, with implementation ideas for Blazor.</summary>
    <published>2026-04-28T16:39:10Z</published>
    <updated>2026-05-08T00:44:47Z</updated>
    <author>
      <name>Ed Charbeneau </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/loading-ui-ux-patterns-ai-applications"/>
    <content type="text"><![CDATA[<p><span class="featured">Give users appropriate loading feedback for the AI process going on. Here are some patterns aligned to the wait time, with implementation ideas for Blazor.</span></p><p>AI-driven applications introduce a new set of challenges for user experience design. Traditional web applications operate within predictable boundaries, where requests complete in a consistent and measurable way. AI features change that dynamic, with response times ranging from near-instant to long-running operations that can take minutes. Designing for that variability is no longer optional, it is a core part of building reliable AI-powered applications.</p><p>The challenge extends beyond implementation into perception. Users do not experience time objectively; they react to how it feels. A blank screen creates doubt, while visible progress keeps the experience grounded. When feedback aligns with the effort required, the interaction feels natural. When it does not, friction builds quickly. Thoughtful loading design addresses this gap by communicating progress, setting expectations and reinforcing the value of the task in motion.</p><p>This guide focuses on practical loading UI patterns for AI scenarios, organized by real-world wait times. Each pattern is implemented with <a target="_blank" href="https://www.telerik.com/blazor-ui">Blazor and Progress Telerik UI components</a>, keeping the discussion grounded in tools developers already use. The goal is simple: create loading experiences that do more than fill time, improving perceived performance and building confidence in the system behind the scenes.
</p><h2>Understanding AI Application Loading Patterns</h2><p>AI applications introduce a different class of loading behavior. Traditional web requests are relatively predictable, but AI workloads are not. Inference, natural language processing and computer vision all introduce variability based on model complexity, input size and system load. As powerful as these capabilities are, they require a more deliberate approach to how progress is communicated.</p><p>Effective loading experiences center on managing expectations through clear, continuous feedback. Every loading state should communicate what is happening now, what has completed and what comes next, keeping users oriented as the system works. With this foundation in place, loading patterns can be tailored to specific wait times, keeping the experience responsive and understandable regardless of how long processing takes.</p><h2>Near Instantaneous Responses (Less Than 1 Second)</h2><p>For interactions under one second, traditional loading indicators often add more friction than value. Quick flashes of spinners or progress bars create a visual glitch that feels broken rather than helpful. Instead, the interface should respond immediately with subtle visual feedback that confirms the action without interrupting flow.</p><p>One exception stands out: when a fast action represents the final step in a longer workflow. In these cases, introducing a brief, intentional delay can create a sense of completion, giving users a moment to recognize the result of their effort before moving on.</p><h3>AI Task Examples</h3><p>AI tasks that typically complete in under one second include:</p><ul><li>Real-time text suggestions and autocomplete</li><li>Simple sentiment analysis on short text</li><li>Basic image classification with lightweight models</li><li>Cached AI responses</li><li>Rule-based chatbot responses</li><li>Simple recommendation filtering</li></ul><h3>Blazor Implementation Strategy</h3><p>Implementing instantaneous feedback in Blazor requires careful coordination between state changes and visual cues. The goal is to reflect user actions immediately while avoiding flicker from overly brief loading states. In practice, this often means enforcing a minimal display duration for indicators so that interactions feel smooth and intentional. </p><p>Try the A/B samples below by clicking the &ldquo;Zoom and Enhance&rdquo; buttons twice. The A sample should feel smoother on short intervals because of the intentional delay.</p><iframe width="100%" height="500px" src="https://blazorrepl.telerik.com/repl/embed/mAkScmvQ58rIAkuM38?editor=true&amp;result=true&amp;errorList=false"></iframe><h3>UX Strategy</h3><p>Instant interactions benefit from confirmation rather than explanation. Subtle cues like button state changes, input highlighting or lightweight animations signal that the system has responded, keeping the experience fluid and uninterrupted. When these signals are consistent and immediate, users stay oriented without feeling like they are waiting.</p><h2>Short Wait Times (1&ndash;3 Seconds)</h2><p>Short waits call for lightweight, indeterminate indicators that acknowledge progress without interrupting flow. Simple spinners or skeleton screens work well because they provide immediate feedback while keeping the interface responsive. More elaborate animations tend to work against this, as users do not have enough time to process them and the interaction can feel slower than it is.</p><p>Skeleton screens are especially effective in AI scenarios because they establish structure upfront. By rendering a placeholder version of the final layout, the interface maintains continuity and gives users a clear sense of what is coming next as content is generated.</p><h3>AI Task Examples</h3><p>AI operations in the 1&ndash;3 second range include:</p><ul><li>Text generation for short responses</li><li>Image enhancement or basic filtering</li><li>Summarizing a document of moderate length</li><li>Translation of short to medium text</li><li>Basic data analysis and insights generation</li><li>Voice-to-text conversion for brief audio</li></ul><h3>Blazor Implementation Strategy</h3><h4>Chat Progress and Thinking</h4><p>The Telerik UI for&nbsp;<a href="https://www.telerik.com/blazor-ui/skeleton" target="_blank">Blazor Skeleton</a> component provides a straightforward way to mirror final layouts while AI-generated content is loading and the agent is thinking.</p><iframe width="100%" height="500px" src="https://blazorrepl.telerik.com/repl/embed/wqkymwFI34Q3Bj4H27?editor=true&amp;result=true&amp;errorList=false"></iframe><h4>Smart Paste Loading</h4><p>The following demo shows a <a href="https://www.telerik.com/blazor-ui/smartpastebutton" target="_blank">smart paste interaction</a> that incorporates loading indicators to signal the process taking place. The Loading Container is displayed over the form fields being populated, while a button loader indicates the action is taking place once the button is clicked.</p><iframe width="100%" height="500px" src="https://blazorrepl.telerik.com/repl/embed/mquemabj125i6dVw38?editor=true&amp;result=true&amp;errorList=false"></iframe><h3>UX Strategy</h3><p>Short waits benefit from clarity without added friction. Contextual messaging that reflects the task in progress, such as &ldquo;Thinking&rdquo; or &ldquo;Generating recommendations,&rdquo; helps users understand what is happening without slowing the experience down. When combined with progressive disclosure, where partial results are rendered as they become available, the interface stays active and responsive, reducing perceived wait time while making the system&rsquo;s work visible.</p><h2>Medium Wait Times (3&ndash;10 Seconds)</h2><p>Users begin to question responsiveness, and a lack of visible progress quickly turns into doubt. Multi-agent workflows or agentic workflows with multiple indeterminate steps can be difficult to navigate. Agents can make requests to external tools that have asynchronous processes. While these processes run, it is important to report the activity in a manageable way without losing focus of the main content.</p><h3>Blazor Implementation</h3><p>Provide feedback for AI operations that expose measurable progress. The example below shows a chat process where the agent uses external tools. Loading indicators are exposed in a collapsible panel to allow the user to see the progress but also hide it when the details are no longer necessary.</p><iframe width="100%" height="500px" src="https://blazorrepl.telerik.com/repl/embed/QqEoGwvA09jzuqBe29?editor=true&amp;result=true&amp;errorList=false"></iframe><h3>UX Strategy</h3><p>Medium waits benefit from a balance of clarity and momentum. Directional time estimates, even something as simple as &ldquo;This may take about a minute,&rdquo; help users decide whether to stay engaged or return later, while continuously moving progress indicators reinforce that the system is still working. When paired with contextual messaging that explains the current step or highlights what is being processed, the experience shifts from passive waiting to a guided interaction that keeps users oriented and informed.</p><h2>Extended Wait Times (10+ Seconds)</h2><p>Extended wait times require a different approach. Clear visibility into progress matters, but so does the freedom to move on without losing track of what is happening. Combining progress indicators with background processing patterns allows the system to stay transparent while keeping the rest of the application usable.</p><p>Percent-complete indicators play a central role here by giving users a sense of scale. Seeing measurable progress helps them judge how much work remains and whether it is worth continuing to wait. At the same time, providing cancel or abort options keeps users in control rather than locked into a long-running process.</p><p>Step-based indicators work especially well for multi-stage AI operations by breaking the process into clear, understandable phases. Even when exact timing is uncertain, each completed step provides direction and reinforces forward movement.</p><h3>Blazor Implementation</h3><p>The following example uses a <a href="https://www.telerik.com/blazor-ui/grid" target="_blank">grid</a> format to display and manage agent activity. Each element displays important information about the task&rsquo;s state. In addition, failure modes are included and made actionable from the interface. This type of UX is great for dashboard-like scenarios where users can launch agent workflows and return later to see the task completion status.</p><iframe width="100%" height="500px" src="https://blazorrepl.telerik.com/repl/embed/QKOSGwlH59MIvvBw27?editor=true&amp;result=true&amp;errorList=false"></iframe><p>If the user may encounter a long and undetermined loading state, a looping indicator can be used to communicate the overall steps that are running in the background, without labeling exactly what stage the process is at now. This nondeterministic progress indicator displays a carousel of information while the user waits. </p><p>With agentic applications, long processes can give the impression of wasted time, even though a process that takes minutes for an agent may have taken a human hour to complete manually. This loading pattern reminds users of the work going on behind the scenes.&nbsp;While some consider this a &ldquo;dark pattern&rdquo; when used to obscure the process details, it can be quite useful when used correctly to share honest information about the value the process provides.</p><p><iframe width="100%" height="500px" src="https://blazorrepl.telerik.com/repl/embed/GKkImcbe33AOXWRF16?editor=true&amp;result=true&amp;errorList=false">&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;nbsp;</iframe></p><p>Extended waits benefit from a design that prioritizes flexibility and user control. Background task patterns, such as persistent status panels or drawers, allow users to continue working while keeping progress visible, reducing the friction of being forced to wait in place. When paired with notification systems that signal completion, the need to actively monitor progress is removed entirely, giving users confidence that the system will follow through.</p><p>Longer waits create space to surface meaningful insights, explain processing steps or provide guidance for improving future results. When these elements are combined effectively, the experience shifts from a blocking delay to a managed, transparent process that keeps users informed without interrupting their workflow.</p><h2>Advanced Patterns for AI Applications</h2><h3>Contextual Loading Messages</h3><p>AI applications benefit from contextual loading messages that explain current processing steps. Replace generic &ldquo;Loading...&rdquo; text with specific descriptions: &ldquo;Analyzing image composition,&rdquo; &ldquo;Generating creative variations&rdquo; or &ldquo;Cross-referencing knowledge base.&rdquo; These messages build user understanding of AI capabilities while setting realistic expectations for output quality.</p><h3>Streaming and Incremental Results</h3><p>Many modern AI applications support streaming responses, particularly for text generation. Implement progressive disclosure patterns that display AI outputs as they generate, reducing perceived wait time while demonstrating active processing. This approach works particularly well for conversational AI interfaces and content-generation tools.</p><h2>Summary</h2><p>Designing loading experiences for AI applications is no longer about handling delays. It is about shaping how users understand and interact with the system. When feedback aligns with the work being performed, users stay engaged and in control regardless of how long processing takes.</p><p>By matching loading strategies to expected wait times, developers can move beyond generic indicators and deliver feedback that is intentional and informative. From immediate visual confirmation to multi-stage progress tracking, each pattern helps make AI interactions feel predictable and responsive.</p><p>Blazor and Telerik UI components provide a practical foundation for implementing these patterns, enabling teams to build consistent loading experiences without introducing unnecessary complexity. The advantage comes from how these tools are applied, not just their availability.</p><p>As AI continues to shape modern applications, loading design becomes a defining part of the user experience. Applications that communicate clearly during processing will feel faster and more reliable, regardless of the work happening behind the scenes.</p><h3>Get Started with These Examples</h3><p>Use any of the Telerik UI for <a target="_blank" href="https://www.telerik.com/blazor-ui">Blazor components</a> shown above, plus the <a target="_blank" href="https://www.telerik.com/blazor-mcp-servers">Blazor MCP servers</a> free with the 30-day trial.</p><p><a href="https://www.telerik.com/try/ui-for-blazor" target="_blank" class="Btn">Try Now</a></p>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:3f5e3e2c-0528-4522-a2c8-b3c294e5861d</id>
    <title type="text">Building Chat Applications with the .NET MAUI Chat (Conversational UI) Control</title>
    <summary type="text">The .NET MAUI Conversational UI (Chat) component allows integrating chat experiences into your mobile and desktop applications. Learn how to work with this chat component, use cases and how to integrate LLM models.</summary>
    <published>2026-04-27T19:45:10Z</published>
    <updated>2026-05-08T00:44:47Z</updated>
    <author>
      <name>Héctor Pérez </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/building-chat-applications-net-maui-chat-conversational-ui-control"/>
    <content type="text"><![CDATA[<p><span class="featured">The .NET MAUI Conversational UI (Chat) component allows integrating chat experiences into your mobile and desktop applications. Learn how to work with this chat component, use cases and how to integrate LLM models.</span></p><p>One of the best ways to connect with a customer is through conversations, whether with a real person or through an AI agent that provides information about the company or solves problems using a knowledge base.</p><p>If you are creating .NET MAUI applications, you should know that in the Progress Telerik suite you can find the <a target="_blank" href="https://www.telerik.com/maui-ui">.NET MAUI Conversational UI (Chat)</a> component, featuring several characteristics that will allow you to create chat-based applications quickly.</p><p>Throughout this post, we will create an app that uses AI models to provide tips on healthy eating. Let&rsquo;s see how to do it!</p><h2 id="understanding-the-radchat-control-from-telerik-for-.net-maui">Understanding the RadChat Control from Telerik for .NET MAUI</h2><p>The <code>RadChat</code> control from Telerik for .NET MAUI is a component that allows users to interact through a conversational interface, production-ready, capable of handling text messages, attachments, voice-to-text integration and full customization so you can adapt it to your own style, among many other features.</p><p>The control is composed of different graphical elements that we can control and modify through code, as shown in the following image:</p><p><img src="https://www.telerik.com/maui-ui/documentation/assets/898f7480b0af1c2627c39907f618d733/chat-visualstructure.png" alt="RadChat Component Visual Structure" /></p><p>Some typical use cases for the control include:</p><ul><li>AI chatbots</li><li>Real-time customer support</li><li>Messaging applications</li><li>Virtual assistants</li><li>Image analysis with vision AI models</li><li>Among many others</li></ul><p>Let&rsquo;s see how to implement the control in a real application.</p><h2 id="setting-up-a-.net-maui-project-to-integrate-chat-conversations">Setting Up a .NET MAUI Project to Integrate Chat Conversations</h2><p>The first and most important thing when using the chat component in our applications is to follow the <a target="_blank" href="https://www.telerik.com/maui-ui/documentation/get-started/first-steps-vs">official installation guide</a>, which shows different ways to set up your environment.</p><p>Additionally, if you want to use any AI model, you need to set up the project by installing the corresponding NuGet packages. As a personal preference, I always like to use <code>Microsoft.Extensions.AI</code>, as it greatly simplifies handling requests to both <strong>OpenAI</strong> and <strong>Azure OpenAI</strong>. For this, install the following packages:</p><ul><li><code>Azure.AI.OpenAI</code></li><li><code>Microsoft.Extensions.AI</code></li><li><code>Microsoft.Extensions.AI.OpenAI</code></li></ul><p>Finally, to facilitate handling with the MVVM pattern, I recommend installing the community toolkit through the following package:</p><ul><li><code>CommunityToolkit.Mvvm</code></li></ul><p>With the packages installed, let&rsquo;s implement the control.</p><h2 id="integrating-chat-functionality-into-a-.net-maui-project">Integrating Chat Functionality into a .NET MAUI Project</h2><p>To use the <code>RadChat</code> control in a .NET MAUI application, you need to do this using the <code>RadChat</code> tag as shown in the following example:</p><pre class=" language-xml"><code class="prism  language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ContentPage</span> <span class="token attr-name">...</span>
    <span class="token attr-name"><span class="token namespace">xmlns:</span>telerik</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>http://schemas.telerik.com/2022/xaml/maui<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>

    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">telerik:</span>RadChat</span> <span class="token attr-name"><span class="token namespace">x:</span>Name</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>chat<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ContentPage</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>By including the above code, we will immediately see the chat control in the emulator:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/basic-radchat-interface.png?sfvrsn=e2cb5709_2" alt="Basic RadChat Interface" /></p><h2 id="interacting-with-chat-messages-in-a-.net-maui-app">Interacting with Chat Messages in a .NET MAUI App</h2><p>To work with chat messages, you should know that the class <code>ChatMessage</code> is the basic class for handling messages, which contains the property <code>Author</code>, used to define the information of a participant that will appear in the UI. <code>Author</code> contains data such as <code>Name</code>, <code>Avatar</code> and <code>Data</code>.</p><p>It is possible to extend this class as the control itself does through <code>TextMessage</code>, which inherits from <code>ChatMessage</code> by adding the property <code>Text</code>.</p><p>Knowing this, we will create a list of <code>TextMessage</code> to maintain the conversation history, as well as define the different roles of type <code>Author</code> that will be used in the conversation. For the example, I have created the class <code>ChatViewModel</code>, which looks as follows:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token keyword">partial</span> <span class="token keyword">class</span> <span class="token class-name">ChatViewModel</span> <span class="token punctuation">:</span> ObservableObject
<span class="token punctuation">{</span>
    <span class="token punctuation">[</span>ObservableProperty<span class="token punctuation">]</span>
    <span class="token keyword">private</span> ObservableCollection<span class="token operator">&lt;</span>TextMessage<span class="token operator">&gt;</span> items <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>

    <span class="token keyword">public</span> Author Me <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword">public</span> Author Bot <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token function">ChatViewModel</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        Me <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Author</span> <span class="token punctuation">{</span> Name <span class="token operator">=</span> <span class="token string">"You"</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>
        Bot <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Author</span> <span class="token punctuation">{</span> Name <span class="token operator">=</span> <span class="token string">"NutriBot"</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>

        Items<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">TextMessage</span>
        <span class="token punctuation">{</span>
            Author <span class="token operator">=</span> Bot<span class="token punctuation">,</span>
            Text <span class="token operator">=</span> <span class="token string">"Hello!  I'm NutriBot, your AI-powered nutrition assistant.\n\n"</span> <span class="token operator">+</span>
                    <span class="token string">"I can help you with:\n"</span> <span class="token operator">+</span>
                    <span class="token string">"Analyzing how healthy a food is\n"</span> <span class="token operator">+</span>
                    <span class="token string">"Analyzing photos of food or nutrition labels\n"</span> <span class="token operator">+</span>
                    <span class="token string">"Providing recommendations for a healthy diet\n"</span> <span class="token operator">+</span>
                    <span class="token string">"Evaluating recipes\n\n"</span> <span class="token operator">+</span>
                    <span class="token string">"Send me a message or a photo to get started!"</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>On the other hand, the RadChat control has the properties <code>Author</code>, which allows specifying who is interacting, in addition to <code>Items</code>, responsible for managing the message history:</p><pre class=" language-xml"><code class="prism  language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">telerik:</span>RadChat</span>
    <span class="token attr-name"><span class="token namespace">x:</span>Name</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>chat<span class="token punctuation">"</span></span>
    <span class="token attr-name">Author</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>{Binding Me}<span class="token punctuation">"</span></span>
    <span class="token attr-name">ItemsSource</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>{Binding Items}<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
</code></pre><p>For the previous viewmodel to work correctly, remember to configure both the code-behind of your page and the dependency injection as follows:</p><p><strong>MauiProgram.cs</strong></p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">MauiProgram</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token keyword">static</span> MauiApp <span class="token function">CreateMauiApp</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">var</span> builder <span class="token operator">=</span> MauiApp<span class="token punctuation">.</span><span class="token function">CreateBuilder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
        builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token generic-method function">AddTransient<span class="token punctuation">&lt;</span>ChatViewModel<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            
        <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
        <span class="token keyword">return</span> builder<span class="token punctuation">.</span><span class="token function">Build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p><strong>MainPage.xaml.cs</strong></p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token function">MainPage</span><span class="token punctuation">(</span>ChatViewModel viewModel<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token function">InitializeComponent</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    BindingContext <span class="token operator">=</span> viewModel<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>Executing the previous code allows you to see the welcome message in the chat history:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/displaying-chat-app-welcome-message.png?sfvrsn=88610ef7_2" alt="Displaying chat app welcome message" /></p><h2 id="receiving-and-returning-chat-messages">Receiving and Returning Chat Messages</h2><p>So far, we have a list of messages in the application; however, there is no real interaction, meaning that messages are not sent or received back. The control has some commands by default that can help us with this task:</p><ul><li><code>SendMessageCommand</code>: executes when a message is sent</li><li><code>PickFileCommand</code>: executes when attempting to attach a file</li><li><code>PickPhotoCommand</code>: executes when wanting to attach a photo</li><li><code>TakePhotoCommand</code>: executes when the camera opens to take a photo</li></ul><p>There are other <a target="_blank" href="https://www.telerik.com/maui-ui/documentation/controls/chat/commands#commands-related-to-attachments">commands for working with attachments</a>, but the main ones are the above.</p><p>Let&rsquo;s implement the functionality to send and receive messages in the viewmodel, creating a property <code>Message</code> that allows binding the user&rsquo;s message, in addition to a method <code>SendMessage</code>, which will be linked to <code>SendMessageCommand</code> as follows:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token keyword">partial</span> <span class="token keyword">class</span> <span class="token class-name">ChatViewModel</span> <span class="token punctuation">:</span> ObservableObject
<span class="token punctuation">{</span>
    <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
    <span class="token punctuation">[</span>ObservableProperty<span class="token punctuation">]</span>
    <span class="token keyword">private</span> <span class="token keyword">string</span> message <span class="token operator">=</span> <span class="token keyword">string</span><span class="token punctuation">.</span>Empty<span class="token punctuation">;</span>
    <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
    
    <span class="token punctuation">[</span>RelayCommand<span class="token punctuation">]</span>
    <span class="token keyword">private</span> <span class="token keyword">async</span> Task <span class="token function">SendMessage</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">var</span> messageText <span class="token operator">=</span> Message<span class="token punctuation">;</span>
        Message <span class="token operator">=</span> <span class="token keyword">string</span><span class="token punctuation">.</span>Empty<span class="token punctuation">;</span>

        Items<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">TextMessage</span> <span class="token punctuation">{</span> Author <span class="token operator">=</span> Me<span class="token punctuation">,</span> Text <span class="token operator">=</span> messageText <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            

        Items<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">TextMessage</span> <span class="token punctuation">{</span> Author <span class="token operator">=</span> Bot<span class="token punctuation">,</span> Text <span class="token operator">=</span> <span class="token string">"Message received!"</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>In the UI page, you need to bind this pair of elements using <code>Message</code> and <code>SendMessageCommand</code>:</p><pre class=" language-xml"><code class="prism  language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">telerik:</span>RadChat</span> <span class="token attr-name">...</span>
    <span class="token attr-name">Message</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>{Binding Message}<span class="token punctuation">"</span></span>
    <span class="token attr-name">SendMessageCommand</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>{Binding SendMessageCommand}<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
</code></pre><p>With these new changes, we can see that there is a response in the chat window after an interaction:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/sending-and-receiving-messages.png?sfvrsn=f503a7ef_2" alt="Sending and receiving messages using predefined commands" /></p><p>Now let&rsquo;s see how to connect an AI model so that we can have more realistic conversations.</p><h2 id="connecting-an-llm-model-to-a-chat-app-in-.net-maui">Connecting an LLM Model to a Chat App in .NET MAUI</h2><p>To use AI in our application, I have created a service-like class that manages the prompt, model information and methods related to obtaining results from the LLM.</p><p>It is worth noting that I have created some variables to store the connection data with the model, solely for demonstration purposes. Ideally, these data should be stored securely on the device, create an external service to handle it, etc.</p><p>The class looks like this:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ChatService</span>
<span class="token punctuation">{</span>
    <span class="token keyword">private</span> <span class="token keyword">const</span> <span class="token keyword">string</span> Endpoint <span class="token operator">=</span> <span class="token string">"your-endpoint"</span><span class="token punctuation">;</span>
    <span class="token keyword">private</span> <span class="token keyword">const</span> <span class="token keyword">string</span> DeploymentName <span class="token operator">=</span> <span class="token string">"your-deployment-name"</span><span class="token punctuation">;</span>
    <span class="token keyword">private</span> <span class="token keyword">const</span> <span class="token keyword">string</span> ApiKey <span class="token operator">=</span> <span class="token string">"your-api-key"</span><span class="token punctuation">;</span>

    <span class="token keyword">private</span> <span class="token keyword">readonly</span> IChatClient _chatClient<span class="token punctuation">;</span>
    <span class="token keyword">private</span> <span class="token keyword">readonly</span> List<span class="token operator">&lt;</span>ChatMessage<span class="token operator">&gt;</span> _history<span class="token punctuation">;</span>

    <span class="token keyword">private</span> <span class="token keyword">const</span> <span class="token keyword">string</span> SystemPrompt <span class="token operator">=</span> <span class="token string">""</span>"
        You are an expert and friendly nutritionist<span class="token punctuation">.</span> Your role <span class="token keyword">is</span> to<span class="token punctuation">:</span>
        <span class="token operator">-</span> Analyze food photographs and nutrition <span class="token function">labels</span> <span class="token punctuation">(</span>nutrition facts<span class="token punctuation">)</span>
        <span class="token operator">-</span> Evaluate how healthy a food <span class="token keyword">is</span> <span class="token keyword">for</span> a balanced diet
        <span class="token operator">-</span> Provide healthy eating recommendations
        <span class="token operator">-</span> Suggest healthier alternatives when necessary
        <span class="token operator">-</span> Analyze recipes and give your professional opinion
        <span class="token operator">-</span> Answer questions about nutrition<span class="token punctuation">,</span> diets<span class="token punctuation">,</span> and food wellness
        
        When analyzing an image<span class="token punctuation">:</span>
        <span class="token number">1</span><span class="token punctuation">.</span> Identify the food or nutrition label
        <span class="token number">2</span><span class="token punctuation">.</span> Give a rating <span class="token keyword">from</span> <span class="token number">1</span><span class="token operator">-</span><span class="token number">10</span> on how healthy it <span class="token keyword">is</span>
        <span class="token number">3</span><span class="token punctuation">.</span> Explain the nutritional pros and cons
        <span class="token number">4</span><span class="token punctuation">.</span> Suggest improvements or alternatives
        
        Always respond <span class="token keyword">in</span> a concise but informative manner<span class="token punctuation">.</span>
        Don't use emojis<span class="token punctuation">.</span>
        <span class="token string">""</span>"<span class="token punctuation">;</span>

    <span class="token keyword">public</span> <span class="token function">ChatService</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        _chatClient <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">AzureOpenAIClient</span><span class="token punctuation">(</span>
                <span class="token keyword">new</span> <span class="token class-name">Uri</span><span class="token punctuation">(</span>Endpoint<span class="token punctuation">)</span><span class="token punctuation">,</span>
                <span class="token keyword">new</span> <span class="token class-name">ApiKeyCredential</span><span class="token punctuation">(</span>ApiKey<span class="token punctuation">)</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">GetChatClient</span><span class="token punctuation">(</span>DeploymentName<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">AsIChatClient</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        _history <span class="token operator">=</span>
        <span class="token punctuation">[</span>
            <span class="token keyword">new</span><span class="token punctuation">(</span>ChatRole<span class="token punctuation">.</span>System<span class="token punctuation">,</span> SystemPrompt<span class="token punctuation">)</span>
        <span class="token punctuation">]</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token comment">/// &lt;summary&gt;</span>
    <span class="token comment">/// Sends a text-only message and returns the AI response.</span>
    <span class="token comment">/// &lt;/summary&gt;</span>
    <span class="token keyword">public</span> <span class="token keyword">async</span> Task<span class="token operator">&lt;</span><span class="token keyword">string</span><span class="token operator">&gt;</span> <span class="token function">SendMessageAsync</span><span class="token punctuation">(</span><span class="token keyword">string</span> userMessage<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        _history<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">new</span><span class="token punctuation">(</span>ChatRole<span class="token punctuation">.</span>User<span class="token punctuation">,</span> userMessage<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">var</span> response <span class="token operator">=</span> <span class="token keyword">await</span> _chatClient<span class="token punctuation">.</span><span class="token function">GetResponseAsync</span><span class="token punctuation">(</span>_history<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">var</span> assistantMessage <span class="token operator">=</span> response<span class="token punctuation">.</span>Text <span class="token operator">?</span><span class="token operator">?</span> <span class="token keyword">string</span><span class="token punctuation">.</span>Empty<span class="token punctuation">;</span>

        _history<span class="token punctuation">.</span><span class="token function">AddMessages</span><span class="token punctuation">(</span>response<span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">return</span> assistantMessage<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token comment">/// &lt;summary&gt;</span>
    <span class="token comment">/// Sends a message with an image for vision analysis and returns the AI response.</span>
    <span class="token comment">/// &lt;/summary&gt;</span>
    <span class="token keyword">public</span> <span class="token keyword">async</span> Task<span class="token operator">&lt;</span><span class="token keyword">string</span><span class="token operator">&gt;</span> <span class="token function">SendMessageWithImageAsync</span><span class="token punctuation">(</span><span class="token keyword">string</span> userMessage<span class="token punctuation">,</span> <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span> imageBytes<span class="token punctuation">,</span> <span class="token keyword">string</span> mimeType<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>        
        <span class="token keyword">var</span> contents <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">List</span><span class="token operator">&lt;</span>AIContent<span class="token operator">&gt;</span>
        <span class="token punctuation">{</span>
            <span class="token keyword">new</span> <span class="token class-name">TextContent</span><span class="token punctuation">(</span><span class="token keyword">string</span><span class="token punctuation">.</span><span class="token function">IsNullOrWhiteSpace</span><span class="token punctuation">(</span>userMessage<span class="token punctuation">)</span>
                <span class="token operator">?</span> <span class="token string">"Analyze this image from a nutritional perspective. How healthy is it?"</span>
                <span class="token punctuation">:</span> userMessage<span class="token punctuation">)</span><span class="token punctuation">,</span>
            <span class="token keyword">new</span> <span class="token class-name">DataContent</span><span class="token punctuation">(</span>imageBytes<span class="token punctuation">,</span> mimeType<span class="token punctuation">)</span>
        <span class="token punctuation">}</span><span class="token punctuation">;</span>

        <span class="token keyword">var</span> message <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ChatMessage</span><span class="token punctuation">(</span>ChatRole<span class="token punctuation">.</span>User<span class="token punctuation">,</span> contents<span class="token punctuation">)</span><span class="token punctuation">;</span>
        _history<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span>message<span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">var</span> response <span class="token operator">=</span> <span class="token keyword">await</span> _chatClient<span class="token punctuation">.</span><span class="token function">GetResponseAsync</span><span class="token punctuation">(</span>_history<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">var</span> assistantMessage <span class="token operator">=</span> response<span class="token punctuation">.</span>Text <span class="token operator">?</span><span class="token operator">?</span> <span class="token keyword">string</span><span class="token punctuation">.</span>Empty<span class="token punctuation">;</span>

        _history<span class="token punctuation">.</span><span class="token function">AddMessages</span><span class="token punctuation">(</span>response<span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">return</span> assistantMessage<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>In the code above, you can see that the methods <code>SendMessageAsync</code> are defined to send only text messages, and <code>SendMessageWithImageAsync</code> to send text along with images, which prepares us for the following sections.</p><p>To use it, we must update the viewmodel code to receive the instance of the new service:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token keyword">partial</span> <span class="token keyword">class</span> <span class="token class-name">ChatViewModel</span> <span class="token punctuation">:</span> ObservableObject
<span class="token punctuation">{</span>
    <span class="token keyword">private</span> <span class="token keyword">readonly</span> ChatService _chatService<span class="token punctuation">;</span>
    <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
    <span class="token keyword">public</span> <span class="token function">ChatViewModel</span><span class="token punctuation">(</span>ChatService chatService<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        _chatService <span class="token operator">=</span> chatService<span class="token punctuation">;</span>
        <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
    <span class="token punctuation">}</span>
    <span class="token punctuation">[</span>RelayCommand<span class="token punctuation">]</span>

    <span class="token keyword">private</span> <span class="token keyword">async</span> Task <span class="token function">SendMessage</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
        <span class="token keyword">try</span>
        <span class="token punctuation">{</span>
            Items<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">TextMessage</span> <span class="token punctuation">{</span> Author <span class="token operator">=</span> Me<span class="token punctuation">,</span> Text <span class="token operator">=</span> messageText <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

            <span class="token keyword">var</span> response <span class="token operator">=</span> <span class="token keyword">await</span> _chatService<span class="token punctuation">.</span><span class="token function">SendMessageAsync</span><span class="token punctuation">(</span>messageText<span class="token punctuation">)</span><span class="token punctuation">;</span>

            Items<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">TextMessage</span> <span class="token punctuation">{</span> Author <span class="token operator">=</span> Bot<span class="token punctuation">,</span> Text <span class="token operator">=</span> response <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
        <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> ex<span class="token punctuation">)</span>
        <span class="token punctuation">{</span>
            Items<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">TextMessage</span>
            <span class="token punctuation">{</span>
                Author <span class="token operator">=</span> Bot<span class="token punctuation">,</span>
                Text <span class="token operator">=</span> $<span class="token string">"⚠️ Error processing your message: {ex.Message}"</span>
            <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>In the previous update, you can also see that I have changed the method <code>SendMessage</code>, adding an <code>try-catch</code> for any error that might occur, in addition to using the service to obtain a response from the LLM model. You should also add the new service to the dependency container in <code>MauiProgram.cs</code>:</p><pre class=" language-csharp"><code class="prism  language-csharp">builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token generic-method function">AddSingleton<span class="token punctuation">&lt;</span>ChatService<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>With the previous changes, we will see a more realistic response created thanks to an LLM model:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/interacting-with-an-llm-model.png?sfvrsn=83fa7495_2" alt="Interacting with an llm model" /></p><h2 id="attaching-elements-to-the-conversation">Attaching Elements to the Conversation</h2><p>Next, we will see how we can attach images to the conversation, with the purpose of querying the AI model for information about them. The first thing we will do is create a model that represents the attachments:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token keyword">partial</span> <span class="token keyword">class</span> <span class="token class-name">AttachedFileData</span> <span class="token punctuation">:</span> ObservableObject
<span class="token punctuation">{</span>
    <span class="token punctuation">[</span>ObservableProperty<span class="token punctuation">]</span>
    <span class="token keyword">private</span> <span class="token keyword">string</span> name <span class="token operator">=</span> <span class="token keyword">string</span><span class="token punctuation">.</span>Empty<span class="token punctuation">;</span>

    <span class="token punctuation">[</span>ObservableProperty<span class="token punctuation">]</span>
    <span class="token keyword">private</span> <span class="token keyword">long</span> size<span class="token punctuation">;</span>

    <span class="token punctuation">[</span>ObservableProperty<span class="token punctuation">]</span>
    <span class="token keyword">private</span> Func<span class="token operator">&lt;</span>Task<span class="token operator">&lt;</span>Stream<span class="token operator">&gt;</span><span class="token operator">&gt;</span> getStream <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">&gt;</span> Task<span class="token punctuation">.</span><span class="token generic-method function">FromResult<span class="token punctuation">&lt;</span>Stream<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span>Stream<span class="token punctuation">.</span>Null<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token punctuation">[</span>ObservableProperty<span class="token punctuation">]</span>
    <span class="token keyword">private</span> <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">?</span> imageBytes<span class="token punctuation">;</span>

    <span class="token punctuation">[</span>ObservableProperty<span class="token punctuation">]</span>
    <span class="token keyword">private</span> <span class="token keyword">string</span><span class="token operator">?</span> mimeType<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>With the class that defines an attachment ready, we can create a list with a generic <code>AttachedFileData</code>, which will allow us to display them in a special section in the chat window.</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token keyword">partial</span> <span class="token keyword">class</span> <span class="token class-name">ChatViewModel</span> <span class="token punctuation">:</span> ObservableObject
<span class="token punctuation">{</span>
    <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
    <span class="token punctuation">[</span>ObservableProperty<span class="token punctuation">]</span>
    <span class="token keyword">private</span> ObservableCollection<span class="token operator">&lt;</span>AttachedFileData<span class="token operator">&gt;</span> attachedFiles <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
    <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
    <span class="token punctuation">[</span>RelayCommand<span class="token punctuation">]</span>
    <span class="token keyword">private</span> <span class="token keyword">async</span> Task <span class="token function">AttachFile</span><span class="token punctuation">(</span>IList<span class="token operator">&lt;</span>AttachedFileData<span class="token operator">&gt;</span><span class="token operator">?</span> filesToAttach<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>filesToAttach <span class="token keyword">is</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span>
        
        <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token keyword">var</span> file <span class="token keyword">in</span> filesToAttach<span class="token punctuation">)</span>
        <span class="token punctuation">{</span>
            AttachedFiles<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span>file<span class="token punctuation">)</span><span class="token punctuation">;</span>                
        <span class="token punctuation">}</span>
        
        filesToAttach<span class="token punctuation">.</span><span class="token function">Clear</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
</code></pre><p>In the code above, we have also defined a method called <code>AttachFile</code>, which will allow us to attach the attachments to the list. In the graphic control, we need to perform two operations.</p><ol><li>Activate the button to attach files via <code>IsMoreButtonVisible</code></li><li>Bind <code>AttachFilesCommand</code> to the viewmodel method</li><li>Bind <code>AttachedFilesSource</code> to <code>AttachedFiles</code></li></ol><p>We can see this below:</p><pre class=" language-xml"><code class="prism  language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">telerik:</span>RadChat</span> <span class="token attr-name">...</span>
    <span class="token attr-name">AttachFilesCommand</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>{Binding AttachFilesCommand}<span class="token punctuation">"</span></span>
    <span class="token attr-name">AttachedFilesSource</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>{Binding AttachedFiles}<span class="token punctuation">"</span></span>
    <span class="token attr-name">IsMoreButtonVisible</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>True<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
</code></pre><p>With the previous modifications, we will see a new button to attach attachments. Although we might think that the above is enough for the LLM to respond correctly to messages with images, if we try to query something with an attached image, we will get a response regarding the LLM model&rsquo;s unawareness of the attached file:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/attaching-files-without-favorable-llm-responses.png?sfvrsn=cabb71e0_2" alt="Attaching files without successful llm response" /></p><p>This happens because we have not added the attached file to the message list. To achieve this, several steps need to be followed:</p><ol><li>Detect when the collection of files changes:</li></ol><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token function">ChatViewModel</span><span class="token punctuation">(</span>ChatService chatService<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
   <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>    
    AttachedFiles<span class="token punctuation">.</span>CollectionChanged <span class="token operator">+</span><span class="token operator">=</span> OnAttachedFilesChanged<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><ol start="2"><li>Create the event handler for when the collection changes. This will allow adding the corresponding information according to the type of file:</li></ol><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">private</span> <span class="token keyword">async</span> <span class="token keyword">void</span> <span class="token function">OnAttachedFilesChanged</span><span class="token punctuation">(</span><span class="token keyword">object</span><span class="token operator">?</span> sender<span class="token punctuation">,</span> NotifyCollectionChangedEventArgs e<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>Action <span class="token operator">==</span> NotifyCollectionChangedAction<span class="token punctuation">.</span>Add <span class="token operator">&amp;&amp;</span> e<span class="token punctuation">.</span>NewItems <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">foreach</span> <span class="token punctuation">(</span>AttachedFileData file <span class="token keyword">in</span> e<span class="token punctuation">.</span>NewItems<span class="token punctuation">)</span>
        <span class="token punctuation">{</span>
            <span class="token keyword">await</span> <span class="token function">LoadFileData</span><span class="token punctuation">(</span>file<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">private</span> <span class="token keyword">async</span> Task <span class="token function">LoadFileData</span><span class="token punctuation">(</span>AttachedFileData file<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">try</span>
    <span class="token punctuation">{</span>
        <span class="token comment">// Load the image bytes from the stream</span>
        <span class="token keyword">using</span> <span class="token keyword">var</span> stream <span class="token operator">=</span> <span class="token keyword">await</span> file<span class="token punctuation">.</span><span class="token function">GetStream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">using</span> <span class="token keyword">var</span> ms <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">MemoryStream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">await</span> stream<span class="token punctuation">.</span><span class="token function">CopyToAsync</span><span class="token punctuation">(</span>ms<span class="token punctuation">)</span><span class="token punctuation">;</span>
        file<span class="token punctuation">.</span>ImageBytes <span class="token operator">=</span> ms<span class="token punctuation">.</span><span class="token function">ToArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token comment">// Try to determine MIME type from file extension</span>
        <span class="token keyword">var</span> extension <span class="token operator">=</span> Path<span class="token punctuation">.</span><span class="token function">GetExtension</span><span class="token punctuation">(</span>file<span class="token punctuation">.</span>Name<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToLowerInvariant</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        file<span class="token punctuation">.</span>MimeType <span class="token operator">=</span> extension <span class="token keyword">switch</span>
        <span class="token punctuation">{</span>
            <span class="token string">".jpg"</span> or <span class="token string">".jpeg"</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token string">"image/jpeg"</span><span class="token punctuation">,</span>
            <span class="token string">".png"</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token string">"image/png"</span><span class="token punctuation">,</span>
            <span class="token string">".gif"</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token string">"image/gif"</span><span class="token punctuation">,</span>
            <span class="token string">".webp"</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token string">"image/webp"</span><span class="token punctuation">,</span>
            _ <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token string">"image/jpeg"</span>
        <span class="token punctuation">}</span><span class="token punctuation">;</span>
                        
    <span class="token punctuation">}</span>
    <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> ex<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        Items<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">TextMessage</span>
        <span class="token punctuation">{</span>
            Author <span class="token operator">=</span> Bot<span class="token punctuation">,</span>
            Text <span class="token operator">=</span> $<span class="token string">"⚠️ Error loading file {file.Name}: {ex.Message}"</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><ol start="3"><li>In addition to the above, we need to modify the method for sending messages. Before doing that, to display the new messages in the history, we need to create a new class to handle the image information. In our case, it is as follows:</li></ol><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token keyword">partial</span> <span class="token keyword">class</span> <span class="token class-name">ChatMessageItem</span> <span class="token punctuation">:</span> ObservableObject
<span class="token punctuation">{</span>
    <span class="token punctuation">[</span>ObservableProperty<span class="token punctuation">]</span>
    <span class="token keyword">private</span> <span class="token keyword">object</span><span class="token operator">?</span> author<span class="token punctuation">;</span>

    <span class="token punctuation">[</span>ObservableProperty<span class="token punctuation">]</span>
    <span class="token keyword">private</span> <span class="token keyword">string</span> text <span class="token operator">=</span> <span class="token keyword">string</span><span class="token punctuation">.</span>Empty<span class="token punctuation">;</span>

    <span class="token punctuation">[</span>ObservableProperty<span class="token punctuation">]</span>
    <span class="token keyword">private</span> <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">?</span> imageData<span class="token punctuation">;</span>

    <span class="token punctuation">[</span>ObservableProperty<span class="token punctuation">]</span>
    <span class="token keyword">private</span> <span class="token keyword">string</span><span class="token operator">?</span> imageMimeType<span class="token punctuation">;</span>

    <span class="token punctuation">[</span>ObservableProperty<span class="token punctuation">]</span>
    <span class="token keyword">private</span> <span class="token keyword">string</span><span class="token operator">?</span> imageFileName<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>With this change, we can now update the method that sends the messages, adding the necessary code for a message to contain information about the image. Make sure to change all references from <code>TextMessage</code> to <code>ChatMessageItem</code>:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token punctuation">[</span>RelayCommand<span class="token punctuation">]</span>
<span class="token keyword">private</span> <span class="token keyword">async</span> Task <span class="token function">SendMessage</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">var</span> messageText <span class="token operator">=</span> Message<span class="token punctuation">;</span>
    <span class="token keyword">var</span> filesToSend <span class="token operator">=</span> AttachedFiles<span class="token punctuation">.</span><span class="token function">ToList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    Message <span class="token operator">=</span> <span class="token keyword">string</span><span class="token punctuation">.</span>Empty<span class="token punctuation">;</span>

    <span class="token keyword">try</span>
    <span class="token punctuation">{</span>        
        <span class="token keyword">if</span> <span class="token punctuation">(</span>filesToSend<span class="token punctuation">.</span>Count <span class="token operator">&gt;</span> <span class="token number">0</span><span class="token punctuation">)</span>
        <span class="token punctuation">{</span>
            <span class="token keyword">var</span> file <span class="token operator">=</span> filesToSend<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
            
            Items<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">ChatMessageItem</span>
            <span class="token punctuation">{</span>
                Author <span class="token operator">=</span> Me<span class="token punctuation">,</span>
                Text <span class="token operator">=</span> <span class="token keyword">string</span><span class="token punctuation">.</span><span class="token function">IsNullOrWhiteSpace</span><span class="token punctuation">(</span>messageText<span class="token punctuation">)</span>
                    <span class="token operator">?</span> <span class="token string">" Analyzing this image..."</span>
                    <span class="token punctuation">:</span> messageText<span class="token punctuation">,</span>
                ImageData <span class="token operator">=</span> file<span class="token punctuation">.</span>ImageBytes<span class="token punctuation">,</span>
                ImageMimeType <span class="token operator">=</span> file<span class="token punctuation">.</span>MimeType<span class="token punctuation">,</span>
                ImageFileName <span class="token operator">=</span> file<span class="token punctuation">.</span>Name
            <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            
            <span class="token keyword">var</span> response <span class="token operator">=</span> <span class="token keyword">await</span> _chatService<span class="token punctuation">.</span><span class="token function">SendMessageWithImageAsync</span><span class="token punctuation">(</span>
                <span class="token keyword">string</span><span class="token punctuation">.</span><span class="token function">IsNullOrWhiteSpace</span><span class="token punctuation">(</span>messageText<span class="token punctuation">)</span>
                    <span class="token operator">?</span> <span class="token string">"Analyze this image from a nutritional perspective."</span>
                    <span class="token punctuation">:</span> messageText<span class="token punctuation">,</span>
                file<span class="token punctuation">.</span>ImageBytes<span class="token operator">!</span><span class="token punctuation">,</span>
                file<span class="token punctuation">.</span>MimeType <span class="token operator">?</span><span class="token operator">?</span> <span class="token string">"image/jpeg"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

            Items<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">ChatMessageItem</span> <span class="token punctuation">{</span> Author <span class="token operator">=</span> Bot<span class="token punctuation">,</span> Text <span class="token operator">=</span> response <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
        <span class="token keyword">else</span>
        <span class="token punctuation">{</span>
           <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
    <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
<span class="token punctuation">}</span>
</code></pre><p>If you try to run the application at this moment, you will encounter an exception like the following:</p><pre class=" language-text"><code class="prism  language-text">`Unable to convert item of type MauiRadChatTests.ChatMessageItem to Telerik.Maui.Controls.Chat.ChatItem. You need to set the ItemConverter of the RadChat`.
</code></pre><p>The message is very descriptive about what we need to do: assign a value to <code>ItemConverter</code>. Let&rsquo;s do that next.</p><h2 id="displaying-attachments-in-the-conversation-history">Displaying Attachments in the Conversation History</h2><p>To understand this section, you should know that the control <code>RadChat</code> works internally with its own types, such as <code>ChatItem</code>, <code>ChatAttachedFile</code>, etc. However, in an MVVM architecture, the viewmodel should not know or depend on specific types in the UI. This is why the control mandates the use of some converters to perform a conversion between business objects and graphical control elements.</p><p><code>ItemConverter</code> is a property we need to assign through a class that inherits from <code>IChatItemConverter</code>. Its purpose is to convert a data model <code>ChatMessageItem</code> (or the type you have defined) into a Telerik UI type <code>ChatItem</code>, so that binding to the property <code>ItemsSource</code> can be done correctly.</p><p>The method <code>ConvertToChatItem</code> is used every time <code>RadChat</code> needs to render an element of the collection <code>ItemSource</code>, while <code>ConvertToDataItem</code> is used when RadChat wants to create a new item automatically, such as when the user presses Send. We will use null because we will handle everything from the viewmodel.</p><p>In the following example, you can see how we compare whether it is a text message or an image, and based on that we return either <code>ChatAttachmentsMessage</code> or <code>TextMessage</code>:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ChatItemConverter</span> <span class="token punctuation">:</span> IChatItemConverter
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> ChatItem <span class="token function">ConvertToChatItem</span><span class="token punctuation">(</span><span class="token keyword">object</span> dataItem<span class="token punctuation">,</span> ChatItemConverterContext context<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">var</span> item <span class="token operator">=</span> <span class="token punctuation">(</span>ChatMessageItem<span class="token punctuation">)</span>dataItem<span class="token punctuation">;</span>

        <span class="token keyword">var</span> vm <span class="token operator">=</span> <span class="token punctuation">(</span>ChatViewModel<span class="token punctuation">)</span>context<span class="token punctuation">.</span>Chat<span class="token punctuation">.</span>BindingContext<span class="token punctuation">;</span>
        <span class="token keyword">var</span> author <span class="token operator">=</span> item<span class="token punctuation">.</span>Author <span class="token operator">==</span> vm<span class="token punctuation">.</span>Bot <span class="token operator">?</span> vm<span class="token punctuation">.</span>Bot <span class="token punctuation">:</span> context<span class="token punctuation">.</span>Chat<span class="token punctuation">.</span>Author<span class="token punctuation">;</span>
        
        <span class="token keyword">if</span> <span class="token punctuation">(</span>item<span class="token punctuation">.</span>ImageData <span class="token operator">!=</span> <span class="token keyword">null</span> <span class="token operator">&amp;&amp;</span> item<span class="token punctuation">.</span>ImageData<span class="token punctuation">.</span>Length <span class="token operator">&gt;</span> <span class="token number">0</span><span class="token punctuation">)</span>
        <span class="token punctuation">{</span>            
            <span class="token keyword">var</span> imageSource <span class="token operator">=</span> ImageSource<span class="token punctuation">.</span><span class="token function">FromStream</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token keyword">new</span> <span class="token class-name">MemoryStream</span><span class="token punctuation">(</span>item<span class="token punctuation">.</span>ImageData<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            
            <span class="token keyword">var</span> attachment <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ChatAttachment</span>
            <span class="token punctuation">{</span>
                FileName <span class="token operator">=</span> item<span class="token punctuation">.</span>ImageFileName <span class="token operator">?</span><span class="token operator">?</span> <span class="token string">"image.jpg"</span><span class="token punctuation">,</span>
                FileSize <span class="token operator">=</span> item<span class="token punctuation">.</span>ImageData<span class="token punctuation">.</span>Length<span class="token punctuation">,</span>
                Data <span class="token operator">=</span> imageSource<span class="token punctuation">,</span>
                GetFileStream <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">&gt;</span> Task<span class="token punctuation">.</span><span class="token generic-method function">FromResult<span class="token punctuation">&lt;</span>Stream<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">MemoryStream</span><span class="token punctuation">(</span>item<span class="token punctuation">.</span>ImageData<span class="token punctuation">)</span><span class="token punctuation">)</span>
            <span class="token punctuation">}</span><span class="token punctuation">;</span>

            <span class="token keyword">var</span> attachmentMessage <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ChatAttachmentsMessage</span>
            <span class="token punctuation">{</span>
                Data <span class="token operator">=</span> dataItem<span class="token punctuation">,</span>
                Author <span class="token operator">=</span> author<span class="token punctuation">,</span>
                Text <span class="token operator">=</span> item<span class="token punctuation">.</span>Text<span class="token punctuation">,</span>
                Attachments <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">List</span><span class="token operator">&lt;</span>ChatAttachment<span class="token operator">&gt;</span> <span class="token punctuation">{</span> attachment <span class="token punctuation">}</span>
            <span class="token punctuation">}</span><span class="token punctuation">;</span>

            <span class="token keyword">return</span> attachmentMessage<span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
        
        <span class="token keyword">var</span> textMessage <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">TextMessage</span>
        <span class="token punctuation">{</span>
            Data <span class="token operator">=</span> dataItem<span class="token punctuation">,</span>
            Author <span class="token operator">=</span> author<span class="token punctuation">,</span>
            Text <span class="token operator">=</span> item<span class="token punctuation">.</span>Text
        <span class="token punctuation">}</span><span class="token punctuation">;</span>

        <span class="token keyword">return</span> textMessage<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">object</span><span class="token operator">?</span> <span class="token function">ConvertToDataItem</span><span class="token punctuation">(</span><span class="token keyword">object</span> message<span class="token punctuation">,</span> ChatItemConverterContext context<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>        
        <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>In the above code, you can see that we are very specific in loading the image through the use of <code>ImageSource.FromStream</code>, necessary to bind a <code>Image</code> control to an image. This converter needs to be bound to the property <code>ItemConverter</code> as we saw in the exception description:</p><pre class=" language-xml"><code class="prism  language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ContentPage</span>
    <span class="token attr-name">...</span>
    <span class="token attr-name"><span class="token namespace">xmlns:</span>converters</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>clr-namespace:MauiRadChatTests<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>


    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ContentPage.Resources</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">converters:</span>ChatItemConverter</span> <span class="token attr-name"><span class="token namespace">x:</span>Key</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>ChatItemConverter<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ContentPage.Resources</span><span class="token punctuation">&gt;</span></span>

    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">telerik:</span>RadChat</span> <span class="token attr-name">...</span>       
        <span class="token attr-name">ItemConverter</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>{StaticResource ChatItemConverter}<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ContentPage</span><span class="token punctuation">&gt;</span></span>

</code></pre><p>Now, if we tried to run the application again, we would receive the following error:</p><pre class=" language-text"><code class="prism  language-text">The AttachedFileConverter is null. This converter must be set, so that the RadChat can automatically convert IFileInfo instances to a business object that represents an attached file, and add them to the AttachedFilesSource collection. Alternatively, you can add attachments objects in your view model via the AttachFilesCommand or AttachFiles event.
</code></pre><p>The previous error tells us that we need to implement a second converter, necessary to convert a business object and add it to the collection of attachments. This means we need to implement a class that implements the <code>IChatAttachedFileConverter</code> interface, which will be responsible for translating our file model <code>AttachedFileData</code> to a <code>ChatAttachedFile</code> from Telerik.</p><p>In our case, the converter looks like this:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">AttachedFileConverter</span> <span class="token punctuation">:</span> IChatAttachedFileConverter
<span class="token punctuation">{</span>
    <span class="token keyword">private</span> <span class="token keyword">static</span> AttachedFileConverter<span class="token operator">?</span> _instance<span class="token punctuation">;</span>
    <span class="token keyword">public</span> <span class="token keyword">static</span> AttachedFileConverter Instance <span class="token operator">=</span><span class="token operator">&gt;</span> _instance <span class="token operator">?</span><span class="token operator">?</span><span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">AttachedFileConverter</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">public</span> ChatAttachedFile <span class="token function">ConvertToChatAttachedFile</span><span class="token punctuation">(</span><span class="token keyword">object</span> dataItem<span class="token punctuation">,</span> ChatAttachedFileConverterContext context<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">var</span> data <span class="token operator">=</span> <span class="token punctuation">(</span>AttachedFileData<span class="token punctuation">)</span>dataItem<span class="token punctuation">;</span>
        <span class="token keyword">var</span> chatAttachedFile <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ChatAttachedFile</span>
        <span class="token punctuation">{</span>
            Data <span class="token operator">=</span> data<span class="token punctuation">,</span>
            FileName <span class="token operator">=</span> data<span class="token punctuation">.</span>Name<span class="token punctuation">,</span>
            FileSize <span class="token operator">=</span> data<span class="token punctuation">.</span>Size
        <span class="token punctuation">}</span><span class="token punctuation">;</span>
        <span class="token keyword">return</span> chatAttachedFile<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">object</span> <span class="token function">ConvertToDataItem</span><span class="token punctuation">(</span>Telerik<span class="token punctuation">.</span>Maui<span class="token punctuation">.</span>Controls<span class="token punctuation">.</span>IFileInfo fileToAttach<span class="token punctuation">,</span> ChatAttachedFileConverterContext context<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">return</span> <span class="token function">CreateAttachedFileData</span><span class="token punctuation">(</span>fileToAttach<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">internal</span> <span class="token keyword">static</span> AttachedFileData <span class="token function">CreateAttachedFileData</span><span class="token punctuation">(</span>Telerik<span class="token punctuation">.</span>Maui<span class="token punctuation">.</span>Controls<span class="token punctuation">.</span>IFileInfo file<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">AttachedFileData</span>
        <span class="token punctuation">{</span>
            Name <span class="token operator">=</span> file<span class="token punctuation">.</span>FileName<span class="token punctuation">,</span>
            Size <span class="token operator">=</span> file<span class="token punctuation">.</span>FileSize<span class="token punctuation">,</span>
            GetStream <span class="token operator">=</span> file<span class="token punctuation">.</span>OpenReadAsync<span class="token punctuation">,</span>
        <span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>In the control, we must assign the instance of the class to <code>AttachedFileConverter</code>, preferably allowing a single instance through the use of <code>x:Static</code>:</p><pre class=" language-xml"><code class="prism  language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">telerik:</span>RadChat</span> <span class="token attr-name">...</span>
    <span class="token attr-name">AttachedFileConverter</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>{x:Static converters:AttachedFileConverter.Instance}<span class="token punctuation">"</span></span><span class="token punctuation">/&gt;</span></span>
</code></pre><p>With the implementation of the previous changes, it is now possible to run the application, which returns information on a query of type image + text:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/radchat-control-in-action-receiving-text-and-images.gif?sfvrsn=7c266654_2" alt="RadChat control in action receiving text and images" /></p><p>With this, we have created a useful and real application based on a chat component, which we developed quickly and easily thanks to the controls from the Telerik suite for .NET MAUI.</p><h2 id="conclusion">Conclusion</h2><p>Throughout this post, we have explored the .NET MAUI Conversational UI (Chat) component, which allows integrating chat experiences into your applications. We have seen how to configure it, the parts that make it up, use cases, how to integrate LLM models for its usage, among other topics.</p><p>This is just the beginning, as in the official documentation you can find other relevant topics about customizing the control. See you in the next post.</p><h3 id="want-to-try-it-yourself">Want to Try It Yourself?</h3><p>The Telerik UI for .NET MAUI component library comes with a free 30-day trial. So go ahead!</p><p><a target="_blank" href="https://www.telerik.com/try/ui-for-maui" class="Btn">Try Now</a></p>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:75015f8b-3de2-4e49-becc-9a6a83db7eeb</id>
    <title type="text">Do Component Libraries Still Matter in the Age of AI?</title>
    <summary type="text">You can have both AI code generation and a solid component library at the foundation. Here are the top six reasons to use both in an integrated approach to code.</summary>
    <published>2026-04-27T19:08:36Z</published>
    <updated>2026-05-08T00:44:47Z</updated>
    <author>
      <name>Kathryn Grayson Nanz </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/do-component-libraries-still-matter-age-ai"/>
    <content type="text"><![CDATA[<p><span class="featured">You can have both AI code generation <em>and </em>a solid component library at the foundation. Here are the top six reasons to use both in an integrated approach to code.</span></p><p>One of the hottest hot takes that I see floating around the developer space these days is <em>&ldquo;Why would I ever use a component library anymore when I can just generate all the components myself with AI?&rdquo;</em> On the surface, it seems like a fairly reasonable question to ask. </p><p>After all, one of the main reasons why developers reached for a component library in the past was to avoid the work of building complex components. And that&rsquo;s fair! As someone who once had to build a color picker from scratch, I get it&mdash;I&rsquo;m not particularly keen on ever repeating that experience, either. </p><p>But now, that&rsquo;s not really a pain point anymore. If I don&rsquo;t want to build that color picker myself, I can just ask my new best friend Claude (or Copilot or Cursor or whatever) to do it for me. In a matter of minutes, I can have my shiny new component with no need to wrangle the code myself. That&rsquo;s the obvious answer &hellip; right? </p><p>Well, maybe not. While it&rsquo;s certainly <em>possible</em> to have every component in your application generated by your AI tool of choice, I&rsquo;d argue that it&rsquo;s not the best solution. Instead, I&rsquo;d say that your code generation will be better if you use AI <em>with</em> component libraries. This doesn&rsquo;t have to be an either/or situation. Why choose when you can have both? Here are my top six reasons why component libraries should remain a core part of your frontend infrastructure, even if you&rsquo;re a full-on vibe coder: </p><h2 id="1-abundant-reference-material">1. Abundant Reference Material</h2><p>AI cannot generate from nothing: the code it creates for you is based on the code samples that it has access to. And you know what creates thousands of standardized examples of the exact same components used in all kinds of different situations? Yep: component libraries. </p><p>When tons of developers are using the same set of components, there&rsquo;s more sample data for the AI to learn from. That means it will be able to reproduce better patterns, make more effective choices and generate fewer mistakes. And this goes beyond just the stuff other developers have built using these components. Any worthwhile component library will also come with pages and pages of documentation. Feature overviews, configuration details, API guides, troubleshooting, recommendations, official demos and sample code&mdash;what more could your coding agent ask for? </p><p>Well, now that you&rsquo;ve asked &hellip; what about third-party reference material? Larger and more popular component libraries will also have unofficial &ldquo;documentation&rdquo; in the form of technical blogs, walkthroughs, videos and sample repos that were created by the community. The longer the component library has been around, the more content will have been created&mdash;and the more content the AI has to reference, the better the output. </p><h2 id="2-better-accessibility">2. Better Accessibility</h2><p>Let&rsquo;s be honest for a moment: <a target="_blank" href="https://frontendmasters.com/blog/ai-generated-ui-is-inaccessible-by-default/">AI code generating tools do not excel at accessibility.</a> They&rsquo;re getting better, and I think it&rsquo;s even fair to say that there will be a time (hopefully in the not-too-distant future) where they might even be good at it. But that day is not today. Until that gap closes, we (the human developers) are still responsible for making the applications and software we build as accessible as possible. </p><p>One of the best ways that we can do that&mdash;and one that the article linked above calls out specifically&mdash;is to leverage libraries that have accessibility built-in on the component level. While it&rsquo;s possible to attempt to include accessibility constraints and instruction in your prompts, giving an AI code generator a set of accessible primitives to work with will get you a lot further. </p><blockquote><p>The deepest solution is architectural. Instead of relying on every prompt to produce correct primitives, use libraries that encode accessibility into their API contracts.</p></blockquote><h2 id="3-cleaner-and-fewer-lines-of-code">3. Cleaner (and Fewer) Lines of Code</h2><p>One of the best parts about using prebuilt components has always been the functionality that comes already baked in. It&rsquo;s not hard to build a basic data grid that displays the content&mdash;the difficulty comes with the virtualization, sorting / filtering / grouping, exporting and more. Each additional feature that you have to build (or generate) is extra code in your codebase that has to be maintained and managed indefinitely. </p><p>Using a component library means you can take advantage of code that someone else is maintaining&mdash;and isn&rsquo;t that the dream, ultimately? Your lines of code go down because it takes fewer lines of code to pass <code>true</code> into the predefined <code>sort</code> property than it does to build a sorting function. &ldquo;But wait!&rdquo; you cry. &ldquo;Why do I care how many lines of code something is if I can just make the AI write those lines of code for me?&rdquo; Well, unfortunately, you do still have to understand, review and maintain those AI-generated lines of code. Nobody is really reading that 2,000+ line PR, but they might read a 200 line one&mdash;isn&rsquo;t that what you&rsquo;d rather deal with? Less stuff generated from scratch means less to review&mdash;and fewer chances for errors to slip in, in the first place. </p><h2 id="4-cost-effectiveness">4. Cost Effectiveness</h2><p>You know what comes with fewer things to generate and fewer revision cycles to correct errors? Fewer tokens. Most AI coding tools charge by token consumption, which means every line of generated code, new prompt and iteration has real cost attached. Complex components with lots of edge cases can take several revision cycles before they&rsquo;re production-ready, and those cycles add up.</p><p>Using a component library helps cut that cost at multiple points. To begin with, you&rsquo;ll write shorter prompts and (as mentioned earlier) generate less code because the AI is handling configuration and composition code rather than implementation code. Telling a well-documented component what to do is a much shorter prompt than asking an AI to build that functionality from scratch. Because the output is more predictable, you&rsquo;ll also spend fewer tokens on corrections. That may not make much of a difference for a single component, but it absolutely scales when you&rsquo;re doing it across an entire application. </p><h2 id="5-easier-human-revision">5. Easier Human Revision</h2><p>Back to human review&mdash;AI won&rsquo;t get it right every time (at least, not yet), which means there will always be places a human needs to step in and make corrections. We already know that it&rsquo;s harder to open up a document you&rsquo;re not familiar with and orient yourself in someone else&rsquo;s code, but that mental lift gets a little easier if you <em>are</em> at least familiar with the tools being used. </p><p>Component libraries offer the benefit of consistent patterns: the same properties, the same naming structures, the same mental model. AI-generated components won&rsquo;t have this kind of standardization, <em>especially</em> if they were generated over time by different developers using different tools. If the AI is generating code using the same set of components every time&mdash;and you&rsquo;re already familiar with those components&mdash;you&rsquo;re going to be able to get up to speed quicker and spend less time figuring out what the AI generated. </p><h2 id="6-alignment-with-design">6. Alignment with Design</h2><p>There&rsquo;s a solid chance that both your development team <em>and</em> the design team are both using generative AI tools in your work&mdash;which can lead to even more painful gaps during the handoff. However, if you&rsquo;re both telling the AI to work with a specific component library, you can start to bring those disparate experiences closer together. Designers might even be able to start vibe coding prototypes they could hand off to a developer! </p><p>Alternately, you could also feed your design system tokens (different kind of tokens) into the AI tool to help it create work in alignment with what already exists. If you&rsquo;re using a tool like Progress ThemeBuilder, designers can go in and customize exactly how each component should look and behave, and then developers can apply that exported CSS to their AI generated layouts&mdash;assuming they&rsquo;re built with the same components. </p><h2 id="build-on-top-of-a-strong-foundation">Build on Top of a Strong Foundation</h2><p>You can always prompt your AI generator of choice and hope for the best&mdash;but from our experience building UI controls, we&rsquo;ve found that the results are better when you can provide your model with the right building blocks. That&rsquo;s why we&rsquo;ve created a suite of AI code generation tools that leverage the Progress Kendo UI and Telerik component libraries, allowing you to generate new layouts, pages and features built with components you know you can trust. </p><p>We believe that AI code generation isn&rsquo;t a replacement for UI controls that are secure, accessible and human-built. Rather than taking the approach that AI should be used <em>instead</em>, our approach is to see where it can be <em>integrated</em>&mdash;to expand the ease of use and capabilities of what we&rsquo;ve already built. </p><p>After all, why throw away a decade of knowledge about building UI controls? For us, it&rsquo;s the ideal foundation to build on top of. </p><p><a href="https://www.telerik.com/mcp-servers#agentic-ui-generator" class="Btn" target="_blank">Try the UI Generator</a></p>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:886c91a4-d681-4b2a-a70f-04ec25db2af6</id>
    <title type="text">Design Principles Unpacked, No. 3: Affordance</title>
    <summary type="text">Good design doesn’t need a manual. Affordance is about making purpose visible—in objects, interfaces and life. Stand out on purpose.</summary>
    <published>2026-04-23T15:57:02Z</published>
    <updated>2026-05-08T00:44:47Z</updated>
    <author>
      <name>Teon Beijl </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/design-principles-unpacked-no-3-affordance"/>
    <content type="text"><![CDATA[<p><span class="featured">Good design doesn&rsquo;t need a manual. Affordance is about making purpose visible&mdash;in objects, interfaces and life. Stand out on purpose.</span></p><p>Every time I see it, it catches my eye. As a designer, I can&rsquo;t help but notice.</p><p>I don&rsquo;t know if you see this where you&rsquo;re from, but here, I see pieces of paper taped to doors: &ldquo;Pull, don&rsquo;t push.&rdquo; Or a coffee machine with a post-it next to the button: &ldquo;Press this first.&rdquo;</p><p>These additions are almost never part of the original design. They appear later as a fix.</p><p>Whenever you see extra instructions layered on top of something, it&rsquo;s usually a sign of poor design. The object didn&rsquo;t make its purpose clear enough on its own.</p><p>In design, we call this <strong>affordance</strong>.</p><h2 id="what-affordance-really-means">What Affordance Really Means</h2><p>Affordance is about perceived purpose. It&rsquo;s the relationship between an object and the actions it suggests. A handle suggests pulling. A flat plate suggests pushing.</p><p>My coffee machine, a Sage, has buttons that light up with LEDs. They highlight which button to press next. Love it! Even my 2-year-old can operate it.</p><p>Good affordance doesn&rsquo;t need explanation. You don&rsquo;t think. You act.</p><p>That&rsquo;s why well-designed objects don&rsquo;t rely on manuals. The design itself guides you toward the intended action.</p><p>When affordance is clear, usage feels natural. When it isn&rsquo;t, we compensate with signs, instructions, warnings and rules.</p><p>And that&rsquo;s where things get interesting.</p><h2 id="when-design-needs-a-manual">When Design Needs a Manual</h2><p>The moment you need to explain how something should be used, the design has already failed.</p><p>We often accept this in physical spaces and in software too. We&rsquo;ve learned to live with bad doors, confusing elevators and interfaces full of hints and labels.</p><p>But we do something similar with people.</p><p>In Dutch, there&rsquo;s a saying that maybe doesn&rsquo;t translate perfectly, but the meaning is clear: <em>&ldquo;That person comes with a manual.&rdquo;</em> We use it when someone is hard to understand. Hard to work with. You need instructions.</p><p>Think about that for a moment. We talk about humans as if they were poorly designed objects that need instructions to function properly.</p><h2 id="affordance-outside-of-design">Affordance Outside of Design</h2><p>This is where the principle starts to matter beyond design.</p><p>Affordance isn&rsquo;t just about usability. It&rsquo;s about recognition and understanding. About purpose. It&rsquo;s about whether others can see what you&rsquo;re capable of without needing a long explanation.</p><p>In work, careers and organizations, we often rely on uniformity to create clarity. Standard resumes, roles and career ladders.</p><p>Uniformity creates predictability. But it doesn&rsquo;t create affordance.</p><p>When everyone looks the same on paper, it becomes harder to see what makes someone valuable. The potential of people is unleveraged.</p><h2 id="standing-out-on-purpose">Standing Out on Purpose</h2><p>Good design sometimes requires contrast. Something has to stand out for affordance to work.</p><p>The same applies to people.</p><p>If you blend in too well, your affordance disappears. Others can&rsquo;t see what you&rsquo;re uniquely good at. Not because you lack value, but because the design doesn&rsquo;t surface it.</p><p>Standing out isn&rsquo;t about being loud. It&rsquo;s about being intentional. I often describe this as <em>standing out on purpose</em>.</p><p>Not for attention. But for clarity.</p><p>When I work with people on career design, the core challenge is rarely skill. It&rsquo;s visibility. Their purpose isn&rsquo;t expressed in a way others can recognize.</p><p>So we redesign how their value is presented. Not by changing who they are, but by emphasizing purpose. Increase the affordance.</p><h2 id="affordance-is-contextual">Affordance Is Contextual</h2><p>Affordance always depends on context.</p><p>A door handle that works in one environment might confuse you in another. The same is true for people.</p><p>&ldquo;Just be yourself&rdquo; sounds good, but it ignores the environment you&rsquo;re operating in. Affordance isn&rsquo;t about self-expression in isolation. It&rsquo;s about how your purpose is perceived within context.</p><p>Good design finds that symbiosis. Clear, functional and purposeful.</p><h2 id="takeaways">Takeaways</h2><p>Affordance teaches us something simple but powerful: If people constantly need instructions to understand you, it&rsquo;s worth asking whether your affordance is clear.</p><p>Not to conform yourself. But to present your value.</p><p>Good design reduces the need for explanation. In objects. In interfaces. And in life.</p><p><strong>Stand out on purpose.</strong></p><hr /><p><strong>Read next:</strong> <a href="https://www.telerik.com/blogs/design-principles-unpacked-no-4-balance" target="_blank">Design Principles Unpacked, No. 4: Balance</a></p>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:89e0e3fc-f615-4718-b937-f4de1ceba07a</id>
    <title type="text">Introducing the Progress Agentic RAG .NET SDK</title>
    <summary type="text">The .NET SDK for Progress Agentic RAG provides Retrieval-Augmented Generation (RAG) capabilities to .NET development with knowledge base management, AI-powered search and resource operations.</summary>
    <published>2026-04-23T15:33:21Z</published>
    <updated>2026-05-08T00:44:47Z</updated>
    <author>
      <name>Ed Charbeneau </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/introducing-progress-agentic-rag-net-sdk"/>
    <content type="text"><![CDATA[<p><span class="featured">The .NET SDK for Progress Agentic RAG provides Retrieval-Augmented Generation (RAG) capabilities to .NET development with knowledge base management, AI-powered search and resource operations.</span></p><h2>What Is Progress Agentic RAG?</h2><p>Progress Agentic RAG is a RAG-as-a-Service that makes it dramatically easier to build AI systems grounded in real, trusted content. Rather than wiring together vector databases, embedding pipelines and retrieval logic yourself, Progress Agentic RAG provides an end-to-end platform for indexing, understanding and retrieving multimodal data.</p><p>It enables intelligent, agent-driven workflows that combine structured knowledge, contextual search and LLM orchestration into a unified experience.</p><p>With the introduction of the .NET SDK for Progress Agentic RAG, .NET developers can integrate this capability directly into their applications with just a few lines of code, leveraging modern .NET architecture patterns such as dependency injection, async workflows and strongly typed APIs.</p><h3>From Retrieval to Agentic Intelligence</h3><p>Progress Agentic RAG offers a fully capable AI Search Dashboard solution that allows you to get started in a matter of minutes. Simply connect or upload your data to a Knowledge Box, wait for NucliaDB&rsquo;s blazing-fast indexing engine to process it, and you&rsquo;re ready to explore grounded, contextual AI responses.</p><p>That immediate productivity is powerful. But modern enterprise development requires more than a dashboard.</p><p>Enterprise environments demand typed APIs, strong tooling and predictable behavior. Many RAG solutions are Python-first, leaving .NET teams stitching together REST calls manually and building custom abstractions just to regain the ergonomics they expect from their platform.</p><p>The new .NET SDK changes that.</p><p>It provides:</p><ul style="margin-top:0in;" type="disc"><li>Strongly typed APIs</li><li>Async-first patterns</li><li>Native .NET integration</li><li>Simplified knowledge base interaction</li></ul><p>With these capabilities, you can move beyond simple retrieval and begin composing intelligent, agent-driven experiences directly inside your application architecture. As powerful as it is convenient, the .NET SDK makes a great choice for new AI-enabled .NET applications.</p><h2>Getting Started</h2><p>Once a Knowledge Box has been established, you can begin interacting with Progress Agentic RAG through the .NET SDK.</p><p>The SDK is distributed as a <a target="_blank" href="https://www.nuget.org/packages/Progress.Nuclia">NuGet package</a> and covers the complete NucliaDB REST API. That includes strongly typed models, structured output helpers, dependency injection extensions and more than 200 APIs that expose the full surface area of the platform. See the <a target="_blank" href="https://docs.rag.progress.cloud/docs/develop/dotnet-sdk/">SDK documentation page </a>for a comprehensive list of service providers available.</p><h3>Install the NuGet Package</h3><pre><code class="language-csharp">dotnet add package Progress.Nuclia</code></pre><p>With the package installed, you can register the INucliaDb interface using modern dependency injection patterns. The SDK supports everything from basic configuration to advanced multi-tenant scenarios using keyed services.</p><h3>Register the Client</h3><pre><code class="language-csharp">using Progress.Nuclia.Extensions;

// Create configuration
var config = new NucliaDbConfig(
    ZoneId: "aws-us-east-2-1",
    KnowledgeBoxId: "your-knowledge-box-id",
    ApiKey: "your-api-key"
);

// Register with logging
builder.Services.AddNucliaDb(config).UseLogging();</code></pre><p>This approach aligns naturally with ASP.NET Core&rsquo;s architecture. You configure once, inject where needed, and keep your AI integration cleanly separated from business logic.</p><h2>Ask Questions with Agentic RAG</h2><p>With configuration complete, you can begin querying your Knowledge Box using AskAsync or AskStreamingAsync.</p><pre><code class="language-csharp">// Make request
AskRequest askRequest = new("What issues are driving the most customer escalations this quarter?");
var response = await client.Search.AskAsync(askRequest);

// Display answer
Console.WriteLine(response.Data.Answer);</code></pre><p>In just a few lines of code, you&rsquo;re executing a grounded, agent-driven query against indexed enterprise data.</p><p>The Ask functionality is only the beginning. With more than 200 APIs available in the SDK, you can ingest and manage resources, create conversational interactions and perform search, all using strongly typed, async-first C# patterns.</p><p>With structured configuration and native .NET integration in place, you can move from experimentation to production-ready AI systems with confidence.</p><h2>Explore the Examples: Blazor and .NET MAUI</h2><p>The fastest way to understand what Progress Agentic RAG can do in a real application is to see it running inside the frameworks you already use.</p><p>We&rsquo;ve published hands-on examples built with:</p><ul style="margin-top:0in;" type="disc"><li><a target="_blank" href="https://github.com/telerik/telerik-blazor-progress-rag-demo">Blazor &mdash; showcasing grounded AI experiences in modern web applications</a></li><li><a target="_blank" href="https://github.com/telerik/telerik-maui-progress-rag-demo">NET MAUI &mdash; demonstrating cross-platform, AI-powered mobile and desktop apps</a></li></ul><p>These samples go beyond simple API calls. They show how to:</p><ul style="margin-top:0in;" type="disc"><li>Register the SDK using dependency injection</li><li>Execute agentic queries with structured output</li><li>Stream responses into interactive UI components</li><li>Keep AI concerns cleanly separated from presentation logic</li></ul><p>If you&rsquo;re building internal tools, customer-facing dashboards or cross-platform AI assistants, these examples provide a production-oriented starting point.</p><p>Clone the samples, wire up your Knowledge Box and see how quickly you can integrate grounded, agent-driven intelligence into your existing .NET architecture.</p><h2>Additional Media</h2><p>Learn more about Progress Agentic RAG and the .NET SDK from video tutorials and podcasts:</p><ul><li>The Progress Agentic RAG .NET SDK was featured on episode 1997 of .NET Rocks:&nbsp;<a target="_blank" href="https://www.dotnetrocks.com/details/1997">.NET Rocks! Agentic RAG with Ed Charbeneau</a></li><li>Getting started videos by Jeff (Csharp) Fritz</li></ul><div sf-youtube-url="https://youtu.be/ilJpB9Y7waA?si=nIm6nTg2HLc0Bvkf"><iframe frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" title="Build Your First AI Search in .NET with Progress Agentic RAG" width="640" height="360" src="https://www.youtube.com/embed/ilJpB9Y7waA?si=nIm6nTg2HLc0Bvkf&amp;enablejsapi=1&amp;origin=https%3A%2F%2Fwww.telerik.com&amp;widgetid=3&amp;forigin=https%3A%2F%2Fwww.telerik.com%2FSitefinity%2Fadminapp%2Fcontent%2Fblogs%2Fc2c9ae54-03a9-4243-86cc-9c50fcc0a754%2Fblogposts%2F89e0e3fc-f615-4718-b937-f4de1ceba07a%2Fedit%3Fsf_provider%3DOpenAccessDataProvider%26sf_culture%3Den&amp;aoriginsup=1&amp;vf=1"></iframe></div><div sf-youtube-url="https://youtu.be/qE6zy75btLo?si=ZferEV00m-sRamDA"><iframe frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" title="Build Your First AI Search in .NET with Progress Agentic RAG Part 2" width="640" height="360" src="https://www.youtube.com/embed/qE6zy75btLo?si=ZferEV00m-sRamDA&amp;enablejsapi=1&amp;origin=https%3A%2F%2Fwww.telerik.com&amp;widgetid=2&amp;forigin=https%3A%2F%2Fwww.telerik.com%2FSitefinity%2Fadminapp%2Fcontent%2Fblogs%2Fc2c9ae54-03a9-4243-86cc-9c50fcc0a754%2Fblogposts%2F89e0e3fc-f615-4718-b937-f4de1ceba07a%2Fedit%3Fsf_provider%3DOpenAccessDataProvider%26sf_culture%3Den&amp;aoriginsup=1&amp;vf=1"></iframe></div>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:ef8e4643-5bc4-4e34-9174-357e1d25de81</id>
    <title type="text">Creating a Custom AI Agent with Telerik Tools 3: Summarizing and Querying</title>
    <summary type="text">Now we’ll add content to the LLM using AI processors from the Progress Telerik Document Processor Libraries to summarize and query our agent’s content.</summary>
    <published>2026-04-22T17:06:35Z</published>
    <updated>2026-05-08T00:44:47Z</updated>
    <author>
      <name>Peter Vogel </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/creating-custom-ai-agent-telerik-tools-3-summarizing-querying"/>
    <content type="text"><![CDATA[<p><span class="featured">Now we&rsquo;ll add content to the LLM using AI processors from the Progress Telerik Document Processor Libraries to summarize and query our agent&rsquo;s content.</span></p><p>In this series of posts, I&rsquo;ve been creating a custom AI Agent using Telerik tools. That&rsquo;s included <a target="_blank" href="https://www.telerik.com/blogs/creating-custom-ai-agent-telerik-tools-1-configuring-llm-azure-ollama">accessing an Azure Large Language Model</a> (LLM) and loading <a target="_blank" href="https://www.telerik.com/blogs/creating-custom-ai-agent-telerik-tools-2-loading-accessing-agent-content">my own content and creating a client to access the LLM</a>.</p><p>The last step is to pass my content to my LLM using one of the Progress Telerik AI processors (part of the <a target="_blank" href="https://docs.telerik.com/devtools/document-processing/introduction">Document Processor Libraries</a>), which is what this post is about.</p><p>The <a target="_blank" href="https://docs.telerik.com/devtools/document-processing/libraries/radwordsprocessing/editing/gen-ai-powered-document-insights/overview">Telerik AI processors</a> support two scenarios for your users: summarizing your agent&rsquo;s content and querying your agent&rsquo;s content.</p><p>To summarize your content, you&rsquo;ll use the Telerik <a target="_blank" href="https://docs.telerik.com/devtools/document-processing/libraries/radwordsprocessing/editing/gen-ai-powered-document-insights/summarization-processor"><code>SummarizationProcess</code></a> processor. For querying content, on the other hand, you have a choice between two processors:</p><ul><li><a target="_blank" href="https://docs.telerik.com/devtools/document-processing/libraries/radwordsprocessing/editing/gen-ai-powered-document-insights/complete-context-question-processor"><code>CompleteContextQuestionProcessor</code></a> which loads all of your content before querying it</li><li><a target="_blank" href="https://docs.telerik.com/devtools/document-processing/libraries/radwordsprocessing/editing/gen-ai-powered-document-insights/partial-context-question-processor"><code>PartialContentQuestionProcessor</code></a> which loads just part of your content as a series of fragments&mdash;a good choice when your content is large and you don&rsquo;t want to allocate (i.e., &ldquo;pay for&rdquo;) enough tokens to load all of it at once</li></ul><p>A token, by the way, represents a word, part of a word or a punctuation mark. The sample document I&rsquo;ll be using to demonstrate the summarization process contains roughly 1,500 words (a relatively small document). So, when summarizing that document, I&rsquo;ll set my token count to 3,500 (I figured doubling the word count to include punctuation marks and adding a 33% buffer would work).</p><p>On the other hand, to demonstrate querying, I&rsquo;ll be loading three documents as my agent&rsquo;s content, totaling about 8,000 words. That&rsquo;s going to require either a larger token count or fragmenting my document.</p><h2 id="summarizing-documents">Summarizing Documents</h2><p>Before you can do any summarizing or querying, you&rsquo;ll need to add the Telerik.Documents.AIConnector NuGet package to your project. With that package added, to summarize your agent&rsquo;s content you&rsquo;ll use the Telerik <code>SummarizationProcessor</code> processor. Your first step is to create a settings object the model that defines the context window for the model by specifying:</p><ul><li>The maximum of number of tokens you&rsquo;re willing to have used in processing the document</li><li>Any text describing how you want to customize the summarization process (e.g., &ldquo;under 100 words,&rdquo; &ldquo;target project managers&rdquo;)</li></ul><p>Once you&rsquo;ve created the settings object, you create a <code>SummarizationProcessor</code> object, passing a chat client (discussed in my last post) and your settings object. Once you&rsquo;ve created the processor, you pass its <code>Summarize</code> method your content and catch the result as a string.</p><p>In the following code, I&rsquo;ve assumed that you&rsquo;ve used Telerik Document Processing Libraries to load a <code>SimpleTextDocument</code> into a variable called <code>std</code> (again, see my previous posts). The code then:</p><ol><li>Creates a chat client tied to an LLM deployed in Azure</li><li>Creates a settings object that tells the summarization processor to
        <ul><li>Accept up to 3,500 tokens</li><li>Asks the processor to summarize the document in less than 100 words</li></ul></li><li>Creates the <code>SummarizationProcessor</code> from the chat client and the settings object</li><li>Passes my content document to the <code>SummarizationProcessor</code>&rsquo;s <code>Summarize</code> method</li><li>Catches the result as a string</li></ol><p>And it&rsquo;s only four lines of code:</p><pre class=" language-csharp"><code class="prism  language-csharp">AzureOpenAIClient aiclt <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span>
                <span class="token keyword">new</span> <span class="token class-name">Uri</span><span class="token punctuation">(</span><span class="token string">"&lt;deployment URL&gt;"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
                <span class="token keyword">new</span> <span class="token class-name">AzureKeyCredential</span><span class="token punctuation">(</span><span class="token string">"&lt;access key&gt;"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
IChatClient chatClt <span class="token operator">=</span> aiclt<span class="token punctuation">.</span><span class="token function">GetChatClient</span><span class="token punctuation">(</span><span class="token string">"&lt;Deployment Name&gt;"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">AsIChatClient</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

SummarizationProcessorSettings spOpts <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token punctuation">(</span><span class="token number">3500</span><span class="token punctuation">,</span> <span class="token string">"Summarize in 100 words"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">using</span> <span class="token punctuation">(</span>SummarizationProcessor sp <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token punctuation">(</span>chatClt<span class="token punctuation">,</span> spOpts<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
   <span class="token keyword">string</span> summary <span class="token operator">=</span> <span class="token keyword">await</span> sp<span class="token punctuation">.</span><span class="token function">Summarize</span><span class="token punctuation">(</span>std<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><h3 id="summarization-results">Summarization Results</h3><p>As an example of what you can expect, I fed my custom agent [a post I wrote on how to use the <code>scrollToItem</code>method]() in the Kendo Grid. You can compare the output to the post to decide how good a job the LLMs did.</p><p>For my first try, I used Azure&rsquo;s gpt-5.2-chat LLM and asked for a summary in 100 words or less. That gave me these 94 words (I&rsquo;ve stripped out the HTML formatting):</p><blockquote><p><code>This article explains how to ensure the right row is visible in a Kendo Grid without forcing users to scroll. It focuses on the `scrollToItem` method, which scrolls directly to a row based on a data item&rsquo;s unique ID. The method works in Angular, ASP.NET MVC, and jQuery grids, with alternatives for React and Blazor. The article walks through configuring an ASP.NET Core project, setting up the grid and data source, enabling scrolling, and invoking `scrollToItem` via JavaScript. It also covers handling invalid selections and loading rows not on the current page using callbacks.</code></p></blockquote><p>I tried the same content using the gpt-oss LLM running in Ollama and got these 114 words:</p><blockquote><p><code>This article explains Kendo Grid&rsquo;s `scrollToItem` method, which brings a specified row into view without manual scrolling. It works across Angular, ASP .NET MVC, and jQuery grids (React and Blazor can use DOM&rsquo;s `scrollIntoView`). The method requires the grid&rsquo;s data model to have a unique `id` field (e.g., `ProductID`) and virtual or endless scrolling enabled. The author demonstrates setting up an ASP .NET Core Razor Page, configuring the grid with a datasource, model, and columns, and creating a textbox/dropdown that triggers `MoveToItem`. The method accepts a callback for invalid IDs or to fetch non‑paged items by calling the callback&rsquo;s `success` with the row index. The result is a row positioned at the top of the grid.</code></p></blockquote><p>I also tried passing just &ldquo;summarize&rdquo; to both LLMs without specifying a word count. The gpt-5.2-chat LLM on Azure gave me back just under 300 words with a pretty good breakdown of the main points in the article. The same prompt with Ollama and gpt-oss gave me just over 400 words but also threw in some of the sample code.</p><p>Finally, I passed &ldquo;provide a title&rdquo; as my requested processing to both LLMs. From gpt-5.2-chat, I got back &ldquo;Using Kendo Grid&rsquo;s scrollToItem to Instantly Bring Data Into View&rdquo; and from gpt-oss, I got &ldquo;Kendo Grid&rsquo;s scrollToItem: A Practical Guide to Quick Row Navigation.&rdquo; I have to admit, I think both of those proposed titles are better than the one I used (feel free to disagree). Not surprisingly, given that I was using the Telerik summarization processor, both engines threw in a summary of the post along with their new title.</p><h2 id="complete-document-queries">Complete Document Queries</h2><p>If you want to let your user ask queries that require looking at your complete document, you can use the Telerik <code>CompleteContextQuestionProcessor</code> processor(which is also the easiest of the two query processors to use).</p><p>To query your agent&rsquo;s content, you just need to:</p><ol><li>Create a <code>CompleteContextProcessorSettings</code> object, passing a maximum token count</li><li>Create a new <code>CompleteContextQuestionProcessor</code> object, passing a chat client and the settings object</li><li>Pass your content and the user&rsquo;s question to the <code>CompleteContextQuestionProcessor</code> object&rsquo;s <code>AnswerQuestion</code> method</li><li>Catch the result as a string</li></ol><p>Typical code will look like this (I&rsquo;ve omitted the code to create the chat client because it&rsquo;s identical to my previous example). Note the larger token count:</p><pre class=" language-csharp"><code class="prism  language-csharp">CompleteContextProcessorSettings caOpts <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token number">9000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">using</span> <span class="token punctuation">(</span>CompleteContextQuestionProcessor ca <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span>chatClient<span class="token punctuation">,</span> caOpts<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
       <span class="token keyword">string</span> answer <span class="token operator">=</span> <span class="token keyword">await</span> ca<span class="token punctuation">.</span><span class="token function">AnswerQuestion</span><span class="token punctuation">(</span>sampleDocument<span class="token punctuation">,</span> prompt<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><h3 id="complete-query-results">Complete Query Results</h3><p>For this example, I loaded three of my posts on how to create asynchronous applications using Azure storage queues (starting with <a target="_blank" href="https://www.telerik.com/blogs/coding-azure-18-creating-securing-azure-storage-queues%22">this Coding Azure 18 post</a>. With that content loaded, I tried asking &ldquo;How do I create a storage queue?&rdquo; using the gpt-5.2-chat LLM and got back this result:</p><pre class=" language-text"><code class="prism  language-text">You create a storage queue in two main steps:
1. Create a storage account
     In the Azure Portal, go to Storage Accounts and select Create.
     Give the storage account a URL friendly name (lowercase, no special characters).
     Choose settings appropriate for queues (Primary service set to Other).
     Complete the wizard and create the storage account.

2. Create the queue inside the storage account
         Open the storage account you just created.
          From the left menu, select Data Storage, then Queues.
          Click + Queue.
           Enter a lowercase, UL friendly queue name.
           Click OK to create the queue.

At that point, the storage queue exists and is ready to be secured and used by applications.
</code></pre><p>Which isn&rsquo;t, I thought, a bad response (though I question the need for the last sentence). I then tried asking &ldquo;What&rsquo;s the minimum code required&rdquo; and the answer I got back began with this text:</p><pre class=" language-text"><code class="prism  language-text">The minimum code required depends on what you are trying to do. The simplest meaningful scenario covered is adding a message to an Azure Storage Queue from a server-side app using a Managed Identity (the recommended production approach).

Assumptions (already configured in Azure):
- The queue already exists
- The App Service has a User Assigned Managed Identity
- That identity has the Storage Queue Data Message Sender role on the queue 
</code></pre><p>That was followed by the code to write out a message to a storage queue (about another 200 words). What I especially liked about response is that the opening cued me (pun intended) to ask follow-up questions. For example, my next prompts would probably be &ldquo;How do I create a User-Assigned Managed Identity?&rdquo; and/or &ldquo;How do I read a message from the queue?&rdquo;</p><h2 id="partial-document-queries">Partial Document Queries</h2><p>However, if you have a very large document (and your LLM is charging you by your token count), you might want to use the Telerik <code>PartialContextQuestionProcessor</code> processor. Using this processor does make an assumption: that you don&rsquo;t need the whole document to answer any one question. And, for a large document that, presumably covers multiple topics, that may be a reasonable assumption.</p><p>Using the <code>PartialContextQuestionProcessor</code> is more complicated than using a <code>CompleteContextQuestionProcessor</code>. First, you&rsquo;ll need to pick an LLM that supports embeddings (I used Azure&rsquo;s text-embedding-3-large LLM). Second, you&rsquo;ll need to provide a custom &ldquo;embedding&rdquo; class that will segment your content into fragments to be processed individually.</p><p>I&rsquo;ve provided a version of the default Telerik &ldquo;embedding&rdquo; class at the end of this post. When you instantiate this class, you&rsquo;ll need to pass:</p><ul><li>The security key for your deployment</li><li>The first segment of the URL for your LLM&rsquo;s deployment (i.e., everything up to the first single forward slash)</li><li>Your deployment&rsquo;s name</li><li>The version of your LLM</li></ul><p>With your &ldquo;embedding&rdquo; class available, to use the <code>PartialContextQuestionProcessor</code> you need to:</p><ol><li>Use the <code>EmbeddingSettingsFactory</code>object&rsquo;s static <code>CreateSettingsForTextDocuments</code> method to create an <code>IEmbeddingSettings</code> object, passing a token count and the name of your deployment</li><li>Create your &ldquo;embedding&rdquo; object</li><li>Create a <code>PartialContextQuestionProcessor</code>, passing a chat client, your embedding object, your settings object and your content</li><li>Pass your query to the <code>PartialContextQuestionProcessor</code> object&rsquo;s <code>Answer</code> method</li><li>Catch the result in a string</li></ol><p>Typical code looks like this (note the lower token count) and is still only four lines of code:</p><pre class=" language-csharp"><code class="prism  language-csharp">IEmbeddingSettings paOpts <span class="token operator">=</span>
       EmbeddingSettingsFactory<span class="token punctuation">.</span><span class="token function">CreateSettingsForTextDocuments</span><span class="token punctuation">(</span><span class="token number">3500</span><span class="token punctuation">,</span> 
                                                                                                                                          &ldquo;<span class="token operator">&lt;</span>deployment name<span class="token operator">&gt;</span>&rdquo;<span class="token punctuation">)</span><span class="token punctuation">;</span>
DefaultAIEmbedder embedder <span class="token operator">=</span>
    <span class="token keyword">new</span> <span class="token class-name">DefaultAIEmbedder</span><span class="token punctuation">(</span>
        <span class="token string">"&lt;key&gt;"</span><span class="token punctuation">,</span>
        <span class="token string">"&lt;deployment URL&gt;"</span><span class="token punctuation">,</span>
        <span class="token string">"&lt;deployment name&gt;"</span><span class="token punctuation">,</span>
        <span class="token string">"&lt;deployment version&gt;"</span>
        <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">using</span> <span class="token punctuation">(</span>PartialContextQuestionProcessor pa <span class="token operator">=</span>
           <span class="token keyword">new</span> <span class="token class-name">PartialContextQuestionProcessor</span><span class="token punctuation">(</span>chatClt<span class="token punctuation">,</span> embedder<span class="token punctuation">,</span> paOpts<span class="token punctuation">,</span> std<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">string</span> answer <span class="token operator">=</span> <span class="token keyword">await</span> pa<span class="token punctuation">.</span><span class="token function">AnswerQuestion</span><span class="token punctuation">(</span>prompt<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><h3 id="partial-query-results">Partial Query Results</h3><p>I then retried the &ldquo;How do I create a storage queue&rdquo; question with the same content I used for the <code>CompleteContextQuestionProcessor</code>. Here&rsquo;s the result I got, which, you&rsquo;ll notice, omits the material on creating the Storage Account that the <code>CompleteContextQuestionProcessor</code> answer included:</p><pre class=" language-text"><code class="prism  language-text">Here is how you create a storage queue in the Azure Portal. First, make sure you already have a Storage Account created that is dedicated to queues.
Then do the following:
1.Go to the Azure Portal.
2. Open your Storage Account.
3. From the menu on the left, select Data Storage, then Queues.
4. On the Queues page, click the + Queue button at the top.
5. Enter a queue name. The name must be lowercase, contain no spaces, and have no special characters. Example: updateproductinventory
6. Click OK.
That&rsquo;s it. The queue is created immediately.
</code></pre><p>In answer to my &ldquo;What is the minimal code&rdquo; question, I got identical code, but the introduction wasn&rsquo;t as comprehensive as with the <code>CompleteContextQuestionProcessor</code> (the prerequisites/assumptions section is terser, for example):</p><pre class=" language-text"><code class="prism  language-text">From the context, the smallest useful interpretation of &ldquo;minimal code&rdquo; is: The minimal code required to add a message to an Azure Storage Queue, assuming the queue already exists and security is already configured.
Below are the minimal working examples for both scenarios described in the context.
</code></pre><p>Still, I thought, a pretty good answer.</p><p>With those three processors (and an LLM and your content) you have all you need to create an AI-enabled backend. The next step is to create a frontend that supports your users interacting with your backend and meets their expectations for an AI-enabled application. That&rsquo;s my next two posts: <a href="https://www.telerik.com/blogs/creating-custom-ai-agent-telerik-tools-4-crafting-a-interactive-blazor-ui">Crafting an Interactive Blazor UI</a> and <a href="https://www.telerik.com/blogs/creating-custom-ai-agent-telerik-tools-5-creating-interactive-ui-javascript">Creating an Interactive UI in JavaScript</a>.</p><p>And here&rsquo;s that default embedder I promised:</p><pre class=" language-csharp"><code class="prism  language-csharp">    <span class="token keyword">internal</span> <span class="token keyword">class</span> <span class="token class-name">DefaultAIEmbedder</span> <span class="token punctuation">:</span> IEmbedder
    <span class="token punctuation">{</span>
        <span class="token keyword">internal</span> <span class="token keyword">readonly</span> HttpClient httpClient<span class="token punctuation">;</span>
        <span class="token keyword">internal</span> <span class="token keyword">readonly</span> <span class="token keyword">string</span> deploymentName<span class="token punctuation">;</span>
        <span class="token keyword">internal</span> <span class="token keyword">readonly</span> <span class="token keyword">string</span> apiVersion<span class="token punctuation">;</span>
        <span class="token keyword">internal</span> <span class="token function">DefaultAIEmbedder</span><span class="token punctuation">(</span><span class="token keyword">string</span> apiKey<span class="token punctuation">,</span> <span class="token keyword">string</span> url<span class="token punctuation">,</span> <span class="token keyword">string</span> deploymentName<span class="token punctuation">,</span> <span class="token keyword">string</span> apiVersion<span class="token punctuation">,</span><span class="token punctuation">)</span>
        <span class="token punctuation">{</span>
            <span class="token keyword">this</span><span class="token punctuation">.</span>deploymentName <span class="token operator">=</span> deploymentName<span class="token punctuation">;</span>
            <span class="token keyword">this</span><span class="token punctuation">.</span>apiVersion <span class="token operator">=</span> apiVersion<span class="token punctuation">;</span>

            <span class="token keyword">this</span><span class="token punctuation">.</span>httpClient <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HttpClient</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token keyword">this</span><span class="token punctuation">.</span>httpClient<span class="token punctuation">.</span>Timeout <span class="token operator">=</span> TimeSpan<span class="token punctuation">.</span><span class="token function">FromMinutes</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token keyword">this</span><span class="token punctuation">.</span>httpClient<span class="token punctuation">.</span>DefaultRequestHeaders<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token string">"api-key"</span><span class="token punctuation">,</span> apiKey<span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token keyword">this</span><span class="token punctuation">.</span>httpClient<span class="token punctuation">.</span>DefaultRequestHeaders<span class="token punctuation">.</span>Accept<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">MediaTypeWithQualityHeaderValue</span><span class="token punctuation">(</span><span class="token string">"application/json"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            
            <span class="token keyword">this</span><span class="token punctuation">.</span>httpClient<span class="token punctuation">.</span>BaseAddress <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Uri</span><span class="token punctuation">(</span>Path<span class="token punctuation">.</span><span class="token function">TrimEndingDirectorySeparator</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>

        <span class="token keyword">public</span> <span class="token keyword">async</span> Task<span class="token operator">&lt;</span>IList<span class="token operator">&lt;</span>Telerik<span class="token punctuation">.</span>Documents<span class="token punctuation">.</span>AI<span class="token punctuation">.</span>Core<span class="token punctuation">.</span>Embedding<span class="token operator">&gt;</span><span class="token operator">&gt;</span> <span class="token function">EmbedAsync</span><span class="token punctuation">(</span>IList<span class="token operator">&lt;</span>IFragment<span class="token operator">&gt;</span> fragments<span class="token punctuation">)</span>
        <span class="token punctuation">{</span>
            AzureEmbeddingsRequest requestBody <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">AzureEmbeddingsRequest</span>
            <span class="token punctuation">{</span>
                Input <span class="token operator">=</span> fragments<span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>p <span class="token operator">=</span><span class="token operator">&gt;</span> p<span class="token punctuation">.</span><span class="token function">ToEmbeddingText</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
                Dimensions <span class="token operator">=</span> <span class="token number">3072</span>
            <span class="token punctuation">}</span><span class="token punctuation">;</span>

            <span class="token keyword">string</span> json <span class="token operator">=</span> JsonSerializer<span class="token punctuation">.</span><span class="token function">Serialize</span><span class="token punctuation">(</span>requestBody<span class="token punctuation">)</span><span class="token punctuation">;</span>
            StringContent content <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StringContent</span><span class="token punctuation">(</span>json<span class="token punctuation">,</span> Encoding<span class="token punctuation">.</span>UTF8<span class="token punctuation">,</span> <span class="token string">"application/json"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

            <span class="token keyword">using</span> HttpResponseMessage response <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span>httpClient<span class="token punctuation">.</span><span class="token function">PostAsync</span><span class="token punctuation">(</span>
                <span class="token string">"openai/deployments/"</span> <span class="token operator">+</span> <span class="token keyword">this</span><span class="token punctuation">.</span>deploymentName <span class="token operator">+</span> <span class="token string">"/embeddings?api-version="</span> <span class="token operator">+</span> <span class="token keyword">this</span><span class="token punctuation">.</span>apiVersion<span class="token punctuation">,</span>
                content<span class="token punctuation">,</span>
                CancellationToken<span class="token punctuation">.</span>None<span class="token punctuation">)</span><span class="token punctuation">;</span>

            Telerik<span class="token punctuation">.</span>Documents<span class="token punctuation">.</span>AI<span class="token punctuation">.</span>Core<span class="token punctuation">.</span>Embedding<span class="token punctuation">[</span><span class="token punctuation">]</span> embeddings <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Telerik<span class="token punctuation">.</span>Documents<span class="token punctuation">.</span>AI<span class="token punctuation">.</span>Core<span class="token punctuation">.</span>Embedding</span><span class="token punctuation">[</span>fragments<span class="token punctuation">.</span>Count<span class="token punctuation">]</span><span class="token punctuation">;</span>

            <span class="token keyword">string</span> responseJson <span class="token operator">=</span> <span class="token keyword">await</span> response<span class="token punctuation">.</span>Content<span class="token punctuation">.</span><span class="token function">ReadAsStringAsync</span><span class="token punctuation">(</span>CancellationToken<span class="token punctuation">.</span>None<span class="token punctuation">)</span><span class="token punctuation">;</span>
            AzureEmbeddingsResponse<span class="token operator">?</span> responseObj <span class="token operator">=</span> JsonSerializer<span class="token punctuation">.</span><span class="token generic-method function">Deserialize<span class="token punctuation">&lt;</span>AzureEmbeddingsResponse<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span>responseJson<span class="token punctuation">)</span><span class="token punctuation">;</span>

            List<span class="token operator">&lt;</span>EmbeddingData<span class="token operator">&gt;</span> sorted <span class="token operator">=</span> responseObj<span class="token operator">!</span><span class="token punctuation">.</span>Data<span class="token punctuation">.</span><span class="token function">OrderBy</span><span class="token punctuation">(</span>d <span class="token operator">=</span><span class="token operator">&gt;</span> d<span class="token punctuation">.</span>Index<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            List<span class="token operator">&lt;</span><span class="token keyword">float</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span> result <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">List</span><span class="token operator">&lt;</span><span class="token keyword">float</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span><span class="token punctuation">(</span>sorted<span class="token punctuation">.</span>Count<span class="token punctuation">)</span><span class="token punctuation">;</span>

            <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> sorted<span class="token punctuation">.</span>Count<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span>
            <span class="token punctuation">{</span>
                EmbeddingData item <span class="token operator">=</span> sorted<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>
                embeddings<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Telerik<span class="token punctuation">.</span>Documents<span class="token punctuation">.</span>AI<span class="token punctuation">.</span>Core<span class="token punctuation">.</span>Embedding</span><span class="token punctuation">(</span>fragments<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">,</span> item<span class="token punctuation">.</span>Embedding<span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span>

            <span class="token keyword">return</span> embeddings<span class="token punctuation">;</span>
        <span class="token punctuation">}</span>

        <span class="token keyword">private</span> <span class="token keyword">sealed</span> <span class="token keyword">class</span> <span class="token class-name">AzureEmbeddingsRequest</span>
        <span class="token punctuation">{</span>
            <span class="token punctuation">[</span>System<span class="token punctuation">.</span>Text<span class="token punctuation">.</span>Json<span class="token punctuation">.</span>Serialization<span class="token punctuation">.</span><span class="token function">JsonPropertyName</span><span class="token punctuation">(</span><span class="token string">"input"</span><span class="token punctuation">)</span><span class="token punctuation">]</span>
            <span class="token keyword">public</span> <span class="token keyword">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span> Input <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> Array<span class="token punctuation">.</span><span class="token generic-method function">Empty<span class="token punctuation">&lt;</span><span class="token keyword">string</span><span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

            <span class="token punctuation">[</span>System<span class="token punctuation">.</span>Text<span class="token punctuation">.</span>Json<span class="token punctuation">.</span>Serialization<span class="token punctuation">.</span><span class="token function">JsonPropertyName</span><span class="token punctuation">(</span><span class="token string">"dimensions"</span><span class="token punctuation">)</span><span class="token punctuation">]</span>
            <span class="token keyword">public</span> <span class="token keyword">int</span><span class="token operator">?</span> Dimensions <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
        <span class="token punctuation">}</span>

        <span class="token keyword">private</span> <span class="token keyword">sealed</span> <span class="token keyword">class</span> <span class="token class-name">AzureEmbeddingsResponse</span>
        <span class="token punctuation">{</span>
            <span class="token punctuation">[</span>System<span class="token punctuation">.</span>Text<span class="token punctuation">.</span>Json<span class="token punctuation">.</span>Serialization<span class="token punctuation">.</span><span class="token function">JsonPropertyName</span><span class="token punctuation">(</span><span class="token string">"data"</span><span class="token punctuation">)</span><span class="token punctuation">]</span>
            <span class="token keyword">public</span> EmbeddingData<span class="token punctuation">[</span><span class="token punctuation">]</span> Data <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> Array<span class="token punctuation">.</span><span class="token generic-method function">Empty<span class="token punctuation">&lt;</span>EmbeddingData<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

            <span class="token punctuation">[</span>System<span class="token punctuation">.</span>Text<span class="token punctuation">.</span>Json<span class="token punctuation">.</span>Serialization<span class="token punctuation">.</span><span class="token function">JsonPropertyName</span><span class="token punctuation">(</span><span class="token string">"model"</span><span class="token punctuation">)</span><span class="token punctuation">]</span>
            <span class="token keyword">public</span> <span class="token keyword">string</span><span class="token operator">?</span> Model <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>

            <span class="token punctuation">[</span>System<span class="token punctuation">.</span>Text<span class="token punctuation">.</span>Json<span class="token punctuation">.</span>Serialization<span class="token punctuation">.</span><span class="token function">JsonPropertyName</span><span class="token punctuation">(</span><span class="token string">"usage"</span><span class="token punctuation">)</span><span class="token punctuation">]</span>
            <span class="token keyword">public</span> UsageInfo<span class="token operator">?</span> Usage <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
        <span class="token punctuation">}</span>

        <span class="token keyword">private</span> <span class="token keyword">sealed</span> <span class="token keyword">class</span> <span class="token class-name">UsageInfo</span>
        <span class="token punctuation">{</span>
            <span class="token punctuation">[</span>System<span class="token punctuation">.</span>Text<span class="token punctuation">.</span>Json<span class="token punctuation">.</span>Serialization<span class="token punctuation">.</span><span class="token function">JsonPropertyName</span><span class="token punctuation">(</span><span class="token string">"prompt_tokens"</span><span class="token punctuation">)</span><span class="token punctuation">]</span>
            <span class="token keyword">public</span> <span class="token keyword">int</span> PromptTokens <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>

            <span class="token punctuation">[</span>System<span class="token punctuation">.</span>Text<span class="token punctuation">.</span>Json<span class="token punctuation">.</span>Serialization<span class="token punctuation">.</span><span class="token function">JsonPropertyName</span><span class="token punctuation">(</span><span class="token string">"total_tokens"</span><span class="token punctuation">)</span><span class="token punctuation">]</span>
            <span class="token keyword">public</span> <span class="token keyword">int</span> TotalTokens <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
        <span class="token punctuation">}</span>

        <span class="token keyword">private</span> <span class="token keyword">sealed</span> <span class="token keyword">class</span> <span class="token class-name">EmbeddingData</span>
        <span class="token punctuation">{</span>
            <span class="token punctuation">[</span>System<span class="token punctuation">.</span>Text<span class="token punctuation">.</span>Json<span class="token punctuation">.</span>Serialization<span class="token punctuation">.</span><span class="token function">JsonPropertyName</span><span class="token punctuation">(</span><span class="token string">"embedding"</span><span class="token punctuation">)</span><span class="token punctuation">]</span>
            <span class="token keyword">public</span> <span class="token keyword">float</span><span class="token punctuation">[</span><span class="token punctuation">]</span> Embedding <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> Array<span class="token punctuation">.</span><span class="token generic-method function">Empty<span class="token punctuation">&lt;</span><span class="token keyword">float</span><span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

            <span class="token punctuation">[</span>System<span class="token punctuation">.</span>Text<span class="token punctuation">.</span>Json<span class="token punctuation">.</span>Serialization<span class="token punctuation">.</span><span class="token function">JsonPropertyName</span><span class="token punctuation">(</span><span class="token string">"index"</span><span class="token punctuation">)</span><span class="token punctuation">]</span>
            <span class="token keyword">public</span> <span class="token keyword">int</span> Index <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>   
</code></pre>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:e67bd1e0-6dce-4ba4-92de-7c7975a18492</id>
    <title type="text">Blazor Basics: Getting Started with Blazor Development in VS Code</title>
    <summary type="text">Learn how to use Visual Studio Code for Blazor web development. VS Code is a free, lightweight code editor, and it is available for Mac and Linux users (unlike its more robust sibling Visual Studio, which is exclusive to Windows).</summary>
    <published>2026-04-21T16:22:45Z</published>
    <updated>2026-05-08T00:44:47Z</updated>
    <author>
      <name>Claudio Bernasconi </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/blazor-basics-getting-started-blazor-development-vs-code"/>
    <content type="text"><![CDATA[<p><span class="featured">Learn how to use Visual Studio Code for Blazor web development. VS Code is a free, lightweight code editor, and it is available for Mac and Linux users (unlike its more robust sibling Visual Studio, which is exclusive to Windows).</span></p><p>While many developers using Windows love the fully integrated development experience Visual Studio provides, it&rsquo;s not an option for some of us.</p><p>We will learn how to set up Visual Studio Code for Blazor web application development, a few first-hand tips and how to conveniently debug.</p><h2 id="why-use-visual-studio-code-for-blazor-web-development">Why Use Visual Studio Code for Blazor Web Development?</h2><p>Maybe you or your team already use <a target="_blank" href="https://code.visualstudio.com/">Visual Studio Code</a> for software development because of its excellent code editor or GIT integration. Why learn something new if you are happy with what you already have?</p><p>Another reason that comes to mind is your preferred operating system.</p><p><a target="_blank" href="https://visualstudio.microsoft.com/">Visual Studio</a> is only available for <strong>Windows</strong>. While I am a Windows user and have always loved the fully fledged feature set provided by Visual Studio for .NET development, I understand that there are other options.</p><p>For <strong>macOS</strong> or <strong>Linux</strong>, you do not have the option to use Visual Studio. Visual Studio Code (VS Code) is a great, lightweight, and free alternative.</p><p>Also, licensing can be an issue. While the Visual Studio Community edition has limitations, VS Code is free for .NET development, both personal and commercial.</p><p>Last but not least, VS Code uses less RAM, hard-drive space and CPU compared to fully fledged IDEs, such as Visual Studio. For hardware constraint situations, it&rsquo;s an ideal choice.</p><h2 id="getting-started-the-setup">Getting Started: The Setup</h2><p>We need two essential things before we can start developing Blazor web applications with VS Code.</p><ol><li>Obviously, we need the latest <a target="_blank" href="https://code.visualstudio.com/">Visual Studio Code</a> version installed</li><li>We also need the <a target="_blank" href="https://dotnet.microsoft.com/">latest .NET SDK</a> installed</li></ol><p>Make sure you can execute the <code>dotnet --version</code> command in a terminal. If it doesn&rsquo;t show the version of the installed <strong>.NET SDK</strong>, add the <strong>.NET CLI</strong> to the <code>PATH</code> variable of your operating system and restart your computer before trying again.</p><h2 id="installing-the-c-dev-kit">Installing the C# Dev Kit</h2><p>Next, we want to install the <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit">C# Dev Kit extension</a> for Visual Studio Code.</p><p><img title="C# Dev Kit Visual Studio Code extension" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/csharp-dev-kit-extension.png?sfvrsn=55b57e85_2" alt="The marketplace page of the C# Dev Kit Visual Studio Code extension." /></p><p>This extension adds C# development features, including project and solution management, debugging, syntax highlighting, code navigation, refactorings and more, to VS Code. It&rsquo;s a mature extension with more than 13 million downloads.</p><blockquote><p><strong>Hint:</strong> The C# Dev Kit extension not only adds support for Blazor web development but for all C# project types, including desktop, mobile, web, or cloud-native applications.</p></blockquote><h2 id="creating-a-blazor-web-app-in-vs-code">Creating a Blazor Web App in VS Code</h2><p>If you open an empty VS Code instance, you&rsquo;ll get the Create .NET Project menu option in the Explorer panel.</p><p><img title="Visual Studio Code: Explorer" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/explorer-create-new-dotnet-project.png?sfvrsn=7487329a_2" alt="The Explorer panel in Visual Studio Code in an empty project shows the Create .NET Project button." /></p><p>Pressing the <strong>Create .NET Project</strong> button will execute a VS Code command that allows you to select the project type. We select the default <strong>Blazor Web App</strong> project template and choose the destination folder on the hard drive.</p><p>Depending on the project template you choose, you&rsquo;ll be able to add additional information to influence the outcome of the created project.</p><p>After a few seconds, the Explorer now shows the project folder containing the created solution file, the project file and the <code>Components</code> folder containing the example pages and layouts.</p><p><img title="VS Code: Explorer" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/visual-studio-code-solution-explorer.png?sfvrsn=72c36ed7_2" alt="The Explorer panel in VS Code in a Blazor Web App shows the folders, such as the Components folder and all the required code files." /></p><blockquote><p><strong>Hint:</strong> We won&rsquo;t explore the structure and architecture of the Blazor Web application.</p></blockquote><p>Another option for creating a .NET project is using the .NET CLI from the terminal. The <code>dotnet new blazor</code> command creates the same project as shown above.</p><h2 id="running-the-blazor-web-application-in-vs-code">Running the Blazor Web Application in VS Code</h2><p>Running a Blazor Web Application is no different from running any other .NET-based application. In a terminal, you can use the <code>dotnet run</code> or <code>dotnet watch run</code> command to run it.</p><p>The <code>dotnet watch run</code> command automatically rebuilds and refreshes the browser (hot reload) when you edit the code and save the file.</p><p><img title="VS Code: Running a Blazor Web App" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/dotnet-watch-run-command.png?sfvrsn=94413184_2" alt="A terminal executing the dotnet watch run command in the folder of a Blazor Web App." /></p><p>The application runs the same whether you run it in Visual Studio or from the command line without VS Code. All three options use the .NET CLI under the hood.</p><p>Compared to Visual Studio, VS Code is noticeably faster at applying simple changes to components, such as the text of the default Home page component.</p><blockquote><p><strong>Tip:</strong> Using hot reload saves a lot of time. Use the <code>dotnet watch run</code> command and open the <code>Counter.razor</code> component. Change the code in the <code>IncrementCount</code> method from <code>currentCount++</code> to <code>currentCount+=2</code> and save the file. Whenever you hit the <strong>Click Me</strong> button, the counter now increases the value by <strong>two</strong> instead of <strong>one</strong>.</p></blockquote><h2 id="debugging-c-code-in-vs-code">Debugging C# Code in VS Code</h2><p>VS Code not only supports running the Blazor web app, but also debugging it.</p><p>To enable debugging:</p><ol><li>Open the <strong>Run and Debug</strong> panel (CTRL+SHIFT+D)</li><li>Click the <strong>Run and Debug</strong> button.</li><li>Select <strong>C#</strong></li><li>Select one of the available project options, or <strong>launch the Startup</strong> project.</li><li>Click on <strong>Start Debugging</strong> (Green Triangle), or press <strong>F5</strong>.</li></ol><p>You can click left of any C# code line to add a breakpoint. A yellow background highlights the code line when the debugger stops the execution. And you can use the toolbar to step through your code and hover over variables to see their values.</p><p><img title="VS Code: Debugging a Blazor Web App" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/debugging-blazor-web-app.png?sfvrsn=dfa894d3_2" alt="The VS Code debugger stopping at a code line and showing the local variables and the toolbar to control the program execution." /></p><p>The debugging experience is comparable to that of other fully fledged IDEs and allows you to find bugs and understand code execution.</p><p>You can add variables to the watch view to keep their values visible as you step through program execution. You also see the call stack whenever the program execution halts, and you can enable or disable the breakpoints.</p><blockquote><p><strong>Hint:</strong> Make sure the application isn&rsquo;t running from a terminal, such as using the dotnet watch run command.</p></blockquote><h2 id="conclusion">Conclusion</h2><p>While I personally prefer using Visual Studio 2026 for most of my development work, I hopefully was able to have shown that Visual Studio Code also offers a first-class development environment.</p><p>For Linux or macOS, VS Code would definitely be my first choice because it provides most of the tools I regularly use and contains much less bloat than a fully fledged IDE. VS Code is free for personal and commercial use.</p><p>Depending on the scope of your project and how you want to work, VS Code is a good choice to get started with Blazor web development. Especially if you already use VS Code for other projects, such as React or Angular web application development.</p><p>If you want to learn more about Blazor development, watch my <a target="_blank" href="https://www.youtube.com/playlist?list=PLwISgxnkpZGL_LhTQCWwp-WCzupv7lcp0">free Blazor Crash Course</a> on YouTube. And stay tuned to the Telerik blog for more <a target="_blank" href="https://www.telerik.com/blogs/tag/blazor-basics">Blazor Basics</a>.</p>]]></content>
  </entry>
</feed>
