<?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:e4becf60-ac59-47f4-83da-b7c5ab78d3a7;id=1279</id>
  <updated>2026-06-18T18:00:59Z</updated>
  <category term="Kendo UI"/>
  <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:7d2311da-a6e9-4adc-9115-599ebd10665b</id>
    <title type="text">Nuxt 4 Server Routes vs. Fetch Composables</title>
    <summary type="text">See a Nuxt app load data from the server in a Server Component and using useFetch to see how Server Components and fetch mechanisms compare.</summary>
    <published>2026-06-18T17:23:23Z</published>
    <updated>2026-06-18T18:00:59Z</updated>
    <author>
      <name>Jonathan Gamble </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/nuxt-4-server-routes-vs-fetch-composables"/>
    <content type="text"><![CDATA[<p><span class="featured">See a Nuxt app load data from the server in a Server Component and using useFetch to see how Server Components and fetch mechanisms compare.</span></p><p>There are currently two ways to load data from the server and hydrate it. Nuxt has Server Components, just like React, as well as fetch mechanisms API route hydration.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/image.png?sfvrsn=74a12829_2" alt="Hello World, server time, test schema, home, server component, fetch" /></p><h2 id="tldr">TL;DR</h2><p>This app will load data from the server in a Server Component and using <code>useFetch</code>. Both mechanisms work similarly from the server perspective, but one method hydrates data from the server and sets it as reactive, while the other caches data from the server. Both components can be validated with <a href="http://schema.org">schema.org</a> to verify the data is properly loaded.</p><h2 id="nuxt-setup">Nuxt Setup</h2><p>We must properly config the layout and components.</p><h3 id="configure-tailwind">Configure Tailwind</h3><p>Follow the <a href="https://tailwindcss.com/docs/installation/framework-guides/nuxt">Nuxt 4 Tailwind Guide</a> for the latest instructions.</p><h3 id="nuxt-config">Nuxt Config</h3><p>Make sure to enable component islands with deep selective client. This enables server components and client components inside of them. For more on Nuxt Server Components, see my other article <a href="https://www.telerik.com/blogs/nuxt-3-server-components-rock">Nuxt 3 Server Components Rock</a>.</p><pre class=" language-tsx"><code class="prism  language-tsx">import tailwindcss from "@tailwindcss/vite";

export default defineNuxtConfig({
  compatibilityDate: "2025-07-15",
  devtools: { enabled: true },
  css: ['./app/assets/css/main.css'],
  experimental: {
    componentIslands: {
      selectiveClient: 'deep',
    },
  },
  vite: {
    plugins: [
      tailwindcss(),
    ],
  },
});
</code></pre><h3 id="configure-appapp.vue-for-pages-and-layouts">Configure app/app.vue for Pages and Layouts</h3><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>NuxtLayout</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>NuxtPage</span> <span class="token punctuation">/&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>NuxtLayout</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">&gt;</span></span>
</code></pre><h3 id="configure-the-layout-at-applayoutsdefault.vue">Configure the Layout at app/layouts/default.vue</h3><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</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>flex flex-col items-center gap-4 p-10<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>slot</span> <span class="token punctuation">/&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>nav</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>flex gap-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>NuxtLink</span> <span class="token attr-name">to</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">active-class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>font-bold<span class="token punctuation">"</span></span> <span class="token attr-name">exact-active-class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>font-bold<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        Home
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>NuxtLink</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>NuxtLink</span>
        <span class="token attr-name">to</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>/server<span class="token punctuation">"</span></span>
        <span class="token attr-name">active-class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>font-bold<span class="token punctuation">"</span></span>
        <span class="token attr-name">exact-active-class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>font-bold<span class="token punctuation">"</span></span>
      <span class="token punctuation">&gt;</span></span>
        Server Component
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>NuxtLink</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>NuxtLink</span>
        <span class="token attr-name">to</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>/fetch<span class="token punctuation">"</span></span>
        <span class="token attr-name">active-class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>font-bold<span class="token punctuation">"</span></span>
        <span class="token attr-name">exact-active-class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>font-bold<span class="token punctuation">"</span></span>
      <span class="token punctuation">&gt;</span></span>
        Fetch
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>NuxtLink</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>nav</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>template</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>This allows bolded current routes.</p><h3 id="homepage-at-apppagesindex.vue">Homepage at app/pages/index.vue</h3><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</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>border p-4 rounded-xl<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    Welcome to the Server Components VS Fetch demo!
  <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>template</span><span class="token punctuation">&gt;</span></span>
</code></pre><h3 id="validate-component-at-appcomponentsvalidate.vue">Validate Component at app/components/validate.vue</h3><p>This is used to create a dynamic link to <a href="http://schema.org">schema.org</a>.</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">lang</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>ts<span class="token punctuation">"</span></span> <span class="token attr-name">setup</span><span class="token punctuation">&gt;</span></span><span class="token script language-javascript">
<span class="token keyword">const</span> currentUrl <span class="token operator">=</span> <span class="token function">useRequestURL</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> href <span class="token operator">=</span> <span class="token function">computed</span><span class="token punctuation">(</span>
  <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span>
    <span class="token template-string"><span class="token string">`https://validator.schema.org/#url=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token function">encodeURIComponent</span><span class="token punctuation">(</span>currentUrl<span class="token punctuation">.</span>href<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">,</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</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">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>underline hover:no-underline<span class="token punctuation">"</span></span> <span class="token attr-name">:href</span><span class="token punctuation">&gt;</span></span>Test Schema<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>template</span><span class="token punctuation">&gt;</span></span>
</code></pre><blockquote><p>Notice <code>useRequestURL()</code> can get the current URL safely.</p></blockquote><h2 id="meta-data-and-schema">Meta Data and Schema</h2><p>Nuxt has a built-in composable, <code>useHead</code>, that allows you to set data directly in the header on the server.</p><pre class=" language-tsx"><code class="prism  language-tsx">useHead({
  meta: [
    {
      property: "article:modified_time",
      content: modifiedTime.data.value,
    },
    {
      name: "last-modified",
      content: modifiedTime.data.value,
    },
  ],
  script: [
    {
      type: "application/ld+json",
      textContent: () =&gt;
        JSON.stringify({
          "@context": "https://schema.org",
          "@type": "WebPage",
          dateModified: modifiedTime.data.value,
        }),
    },
  ],
});
</code></pre><p>You can create a <code>meta</code> tag with the <code>meta</code> property, and set the data that you see fit. This is imperative for SEO. Modern search engines group data using JSON-LD and <a href="http://schema.org">schema.org</a>, which requires a formatted <code>script</code> tag.</p><p>The following examples will set the modified date as the fetch date. This is technically incorrect, as you would want this static, but you can see the usefulness of this for dynamically generated pages with good SEO techniques.</p><h2 id="fetch-with-useasyncdata">Fetch with useAsyncData</h2><p>You can fetch data with <code>useFetch</code> if you have an endpoint, or <code>useAsyncData</code> if you just want to run a safe function directly on the server.</p><h3 id="basic-fetch-page-at-apppagesfetch.vue">Basic Fetch Page at app/pages/fetch.vue</h3><pre class=" language-tsx"><code class="prism  language-tsx">&lt;template&gt;Hello World! &lt;Server-Time-Client /&gt;&lt;/template&gt;
</code></pre><blockquote><p>I love not having to manually import components in the <code>components</code> directory!</p></blockquote><h3 id="here-we-create-appcomponentsserver-time-client.vue">Here We Create app/components/server-time-client.vue</h3><pre class=" language-tsx"><code class="prism  language-tsx">&lt;script setup lang="ts"&gt;
const modifiedTime = await useAsyncData("time", async () =&gt; {
  return new Date().toISOString();
});

useHead({
  meta: [
    {
      property: "article:modified_time",
      content: modifiedTime.data.value,
    },
    {
      name: "last-modified",
      content: modifiedTime.data.value,
    },
  ],
  script: [
    {
      type: "application/ld+json",
      textContent: () =&gt;
        JSON.stringify({
          "@context": "https://schema.org",
          "@type": "WebPage",
          dateModified: modifiedTime.data.value,
        }),
    },
  ],
});
&lt;/script&gt;

&lt;template&gt;
  &lt;div v-if="modifiedTime.data"&gt;Server time: {{ modifiedTime.data.value }}&lt;/div&gt;
  &lt;Validate /&gt;
&lt;/template&gt;
</code></pre><ul><li>We don&rsquo;t have to have <code>client</code> in the name, but it is good to differentiate in this example.</li><li>With <code>useAsyncData</code>, we load data on the server and hydrate it to the browser keeping it reactive. We could have equally created an endpoint at <code>server/api/time.ts</code> and called it with <code>useFetch</code>.</li><li>Your real data will probably be async and will pull from a database.</li></ul><h2 id="server-component">Server Component</h2><p>We can also load the data directly in the HTML on the server, similar to PHP. It will NOT be reactive.</p><h3 id="basic-server-page-at-apppagesserver.vue">Basic Server Page at app/pages/server.vue</h3><pre class=" language-tsx"><code class="prism  language-tsx">&lt;template&gt;Hello World! &lt;Server-Time&gt;&lt;Validate /&gt;&lt;/Server-Time&gt;&lt;/template&gt;
</code></pre><p>We are loading a client component, <code>validate</code>, inside a server component. This is reactive and depends on the current URL. It must use a <code>&lt;slot /&gt;</code> to work correctly. This is why we enabled <code>deep</code> in <code>nuxt.config.ts</code>. Again, see <a href="https://www.telerik.com/blogs/nuxt-3-server-components-rock">Nuxt 3 Server Components Rock</a>.</p><h3 id="server-time-component-at-appcomponentsserver-time.server.vue">Server Time Component at app/components/server-time.server.vue</h3><pre class=" language-tsx"><code class="prism  language-tsx">&lt;script setup lang="ts"&gt;
const modifiedTime = new Date().toISOString();

useHead({
  meta: [
    {
      property: "article:modified_time",
      content: modifiedTime,
    },
    {
      name: "last-modified",
      content: modifiedTime,
    },
  ],
  script: [
    {
      type: "application/ld+json",
      textContent: () =&gt;
        JSON.stringify({
          "@context": "https://schema.org",
          "@type": "WebPage",
          dateModified: modifiedTime,
        }),
    },
  ],
});
&lt;/script&gt;

&lt;template&gt;
  &lt;div class="flex flex-col gap-4 items-center"&gt;
    &lt;div&gt;Server time: {{ modifiedTime }}&lt;/div&gt;
    &lt;slot /&gt;
  &lt;/div&gt;
&lt;/template&gt;
</code></pre><ul><li>A Server Component <em>must</em> have the <code>.server.ts</code> extension to load correctly.</li><li>We can just put our server code directly in the header. None of the JavaScript gets loaded in the browser for this component.</li></ul><h2 id="comparison">Comparison</h2><p>So what is the difference?</p><table><style>table, th, td {
  border: 1px;
  border-color: #bdbdba;
  border-style: dotted;
  border-collapse: collapse;
  margin-right: auto;
  cellspacing="5";
  text-align :left;
}
</style>
 <thead><tr><th style="width:33.3333%;padding:5px;">Aspect</th><th style="width:33.3333%;padding:5px;">Server components</th><th style="width:33.3333%;padding:5px;"><code>useFetch</code> / <code>useAsyncData</code></th></tr></thead><tbody><tr><td style="width:33.3333%;padding:5px;">Main purpose</td><td style="width:33.3333%;padding:5px;">Render UI on server</td><td style="width:33.3333%;padding:5px;">Load reactive data</td></tr><tr><td style="width:33.3333%;padding:5px;">Output</td><td style="width:33.3333%;padding:5px;">HTML</td><td style="width:33.3333%;padding:5px;">Data refs</td></tr><tr><td style="width:33.3333%;padding:5px;">Client JS</td><td style="width:33.3333%;padding:5px;">Less / none for island</td><td style="width:33.3333%;padding:5px;">Normal hydrated component</td></tr><tr><td style="width:33.3333%;padding:5px;">Best for</td><td style="width:33.3333%;padding:5px;">Static or mostly static UI</td><td style="width:33.3333%;padding:5px;">Interactive data-driven UI</td></tr><tr><td style="width:33.3333%;padding:5px;">Data state</td><td style="width:33.3333%;padding:5px;">Props in, HTML out</td><td style="width:33.3333%;padding:5px;"><code>data</code>, <code>pending</code>, <code>error</code></td></tr><tr><td style="width:33.3333%;padding:5px;">Refresh behavior</td><td style="width:33.3333%;padding:5px;">Re-render island</td><td style="width:33.3333%;padding:5px;"><code>refresh()</code> / reactive reload</td></tr><tr><td style="width:33.3333%;padding:5px;">Good example</td><td style="width:33.3333%;padding:5px;">Server-rendered timestamp block</td><td style="width:33.3333%;padding:5px;">Posts, user, notes, lists</td></tr></tbody></table><br /><p>Generally speaking, Fetch Composables can be reactive and load JavaScript. Server Components just load the data as if it were static HTML. This is clear when you navigate between pages, the Server Component does not reload the date, while the Fetch Component does.</p><h3 id="testing-schema">Testing Schema</h3><p>You can click the <code>test schema</code> button to verify the data is loaded from the server and not client, and that it loads correctly.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/image-1.png?sfvrsn=22412d3d_2" alt="webpage 0 errors" /></p><p>If you&rsquo;re loading something quickly, use Server Components. If you need dynamic data, use Fetch Composables.</p><p>That&rsquo;s a wrap!</p><p><strong>Repo:</strong> <a href="https://github.com/jdgamble555/nuxt-server-vs-middleware">GitHub</a><br /><strong>Demo:</strong> <a href="https://nuxt-server-vs-middleware.vercel.app/">Vercel</a>
</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">Pure Surreal Database Authentication in Nuxt</h4></div><div class="col-8"><p class="u-fs16 u-mb0">See how <a target="_blank" href="https://www.telerik.com/blogs/pure-surreal-database-authentication-nuxt">Nuxt pairs up with Surreal Database</a> with a sample app enabling login, register, logout and password change options with route guards and server-safe APIs.</p></div></div></aside>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:d743cc9b-2128-476b-86e3-978e5c13f2e4</id>
    <title type="text">When Status OK Is Still a Failure: A Taxonomy of Silent AI Agent Breakage and How to Detect It</title>
    <summary type="text">The Progress AI Observability Python SDK can help debug AI agent tasks that fail despite component reports claiming success.</summary>
    <published>2026-06-18T14:54:15Z</published>
    <updated>2026-06-18T18:00:59Z</updated>
    <author>
      <name>Nikolay Iliev </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/when-status-ok-still-failure-taxonomy-silent-ai-agent-breakage-how-detect"/>
    <content type="text"><![CDATA[<p><span class="featured">The Progress AI Observability Python SDK can help debug AI agent tasks that fail despite component reports claiming success.</span></p><p><strong>TL;DR:</strong> Production AI agents exhibit a failure class that traditional monitoring cannot detect: tasks that fail while every component reports success. This post walks through a concrete booking agent scenario, shows how to instrument it with the Progress AI Observability Python SDK, and demonstrates the debugging workflow from &ldquo;user got a wrong answer&rdquo; to &ldquo;root cause identified in 30 seconds.&rdquo;</p><h2 id="the-silent-failure-class">The Silent Failure Class</h2><p>In distributed systems, we distinguish between <strong>crash failures</strong> (a node stops responding) and <strong>Byzantine failures</strong> (a node responds incorrectly). AI agents introduce a third class: <strong>semantic failures</strong>, where every component operates correctly at the protocol level but the composed result is wrong.</p><p>This is not an edge case. Community signals from Reddit (<a target="_blank" href="https://www.reddit.com/r/AI_Agents/">r/AI_Agents</a> debugging threads), <a target="_blank" href="https://news.ycombinator.com/">Hacker News</a> (MCP observability discussions) and engineering blogs (<a target="_blank" href="https://leaddev.com/">LeadDev</a>&rsquo;s &ldquo;<a target="_blank" href="https://leaddev.com/ai/observability-tools-werent-built-for-ai-debugging">Observability tools weren&rsquo;t built for AI debugging</a>&rdquo;) all describe the same pattern: agents that pass every health check while delivering fundamentally broken results.</p><p>The core problem: <strong>HTTP status codes and exception-based monitoring operate at the transport layer. Agent task success operates at the semantic layer. There is no bridge between them unless you build one.</strong></p><h2 id="why-this-is-harder-than-microservices-debugging">Why This Is Harder Than Microservices Debugging</h2><p>In traditional microservices, a degraded dependency typically causes visible cascading failures. Service A calls Service B; Service B is slow; Service A times out; your dashboard shows errors. The failure propagates visibly through the system.</p><p>AI agents absorb degraded dependencies. The hotel API returns a partial result set, and the LLM smoothly generates a recommendation anyway. No timeout. No error. No cascade. The failure is absorbed by the LLM&rsquo;s ability to produce confident text regardless of input quality. This absorption property is what makes LLMs useful (they handle messy inputs gracefully) and simultaneously what makes them dangerous to debug (they hide upstream problems behind fluent language).</p><p>The implication: you cannot rely on error propagation to surface problems. You must explicitly check data quality at every boundary between components. That is what the validation layer does, and that is what makes trace-based observability with content capture the right tool for the job.</p><h2 id="what-content-tracing-means-and-why-its-so-important">What Content Tracing Means and Why It&rsquo;s So Important</h2><p>Content tracing means capturing the actual inputs and outputs that move through an agent workflow: tool arguments, tool responses, prompts, model responses and validation results. Standard tracing tells you that a span ran, how long it took and whether it errored. Content tracing shows what data the span operated on.</p><p>That distinction matters because silent failures happen in the data, not in the status code. A hotel search span that says <code>200 OK, 300ms</code> only tells you the API responded. A content-aware span that shows <code>providers_responded: 1</code> and <code>partial: True</code> tells you the agent had incomplete hotel data to work with.</p><p>In this post, content tracing is the foundation. Validation spans add domain-specific judgment on top of captured content, and evaluation tasks use those signals to detect quality problems over time.</p><h2 id="the-scenario-a-booking-agent-that-looks-fine">The Scenario: A Booking Agent That Looks Fine</h2><p>Let&rsquo;s work with a concrete example throughout this post. A travel booking agent handles user requests by calling hotel search and preference tools, then synthesizing a response with an LLM. The agent runs in production. Monitoring shows green. Then a user complains: &ldquo;The agent recommended Budget Inn as my only hotel option in Barcelona, but I found many better places elsewhere.&rdquo;</p><p>What happened? The hotel API queried three upstream providers. Two timed out internally, so it returned a valid partial payload with one poor option and metadata indicating <code>"partial": True</code> instead of an error. The LLM received that degraded hotel data and generated a helpful-sounding, completely wrong recommendation. The agent returned status &ldquo;success.&rdquo; Nobody knew anything broke until the user complained.</p><h2 id="the-three-silent-failure-modes-in-this-agent">The Three Silent Failure Modes in This Agent</h2><p>Before we instrument anything, let&rsquo;s understand what can go wrong without raising an exception:</p><h3 id="empty-results-from-a-degraded-provider">Empty Results from a Degraded Provider</h3><p>The hotel API returns HTTP 200 with <code>"results": []</code>. The provider is degraded (not down), so it sends back a valid but empty response. The agent treats this as &ldquo;no hotels are available&rdquo; rather than &ldquo;I couldn&rsquo;t get hotel data.&rdquo;</p><h3 id="partial-aggregation">Partial Aggregation</h3><p>The hotel API queries three upstream providers. Two time out internally. The API returns the one result it got, along with a metadata flag <code>"partial": True</code> that the agent never checks. The user sees one bad option and assumes nothing better exists.</p><h3 id="stale-hotel-inventory">Stale Hotel Inventory</h3><p>A hotel inventory endpoint serves cached availability and pricing data that is 30 days old. The agent recommends a property that looks cheap in the cached payload, but the price is outdated and better current options exist.</p><p>All three produce identical behavior at the HTTP level: 200 OK, valid JSON, no exceptions. The difference between &ldquo;working&rdquo; and &ldquo;broken&rdquo; is entirely in the content of the response payload.</p><h2 id="the-observability-workflow">The Observability Workflow</h2><p>Before we add instrumentation, it helps to name the process we are trying to make visible. One user request creates one agent execution. In this booking-agent example, that execution calls several external tools. Each tool can return a protocol-level success while still returning semantically bad data: an empty hotel result set, a partial provider response or stale hotel inventory.</p><p>The observability pattern is:</p><ol><li><p>Agent execution starts from a user request.</p></li><li><p>The agent makes one or more external tool calls.</p></li><li><p>We capture and validate the content returned by those tools.</p></li><li><p>The full execution is recorded as a trace.</p></li><li><p>That recorded trace supports two complementary workflows:</p><ul><li>Reactive debugging: an engineer inspects a suspicious trace.</li><li>Proactive detection: an evaluation task scores matching spans over time.</li></ul></li></ol><p>In practice, that means we do not just trace whether each tool returned 200 OK. We capture the content returned by each tool, validate that content against domain expectations, and record the validation result as part of the trace. Later, when an engineer opens a failed or suspicious agent run, they can inspect the trace tree and see exactly where meaningful data quality broke down.</p><p>The same validation signal can then be used proactively. Instead of waiting for a user complaint, evaluation tasks can score the matching LLM spans within incoming traces and surface patterns for poor outputs such as empty hotel results, partial hotel responses or stale hotel inventory data before they become a visible customer issue.</p><h2 id="instrumenting-the-agent">Instrumenting the Agent</h2><p>Now that we have defined the observability workflow, we can translate it into code. The goal of instrumentation is not just to record that the booking agent ran. It is to capture the data moving through the agent at the points where silent failures occur: external tool calls, validation checks and the final LLM response.</p><p>In this demo, instrumentation has three layers:</p><ul><li>Initialize observability with content tracing enabled.</li><li>Decorate external tools so their inputs and outputs become spans.</li><li>Add validation tasks that record whether each tool response is semantically usable.</li></ul><p>The full demo is available in <strong><a href="https://github.com/NickIliev/silent-failure-demo" target="_blank">silent_failure_demo.py</a></strong> alongside this post. The sections below walk through the key instrumentation decisions piece by piece, starting with the configuration that makes the rest of the workflow possible.</p><h3 id="initialization-content-tracing-is-the-foundational-first-step">Initialization: Content Tracing Is the Foundational First Step</h3><pre class=" language-python"><code class="prism  language-python"><span class="token keyword">from</span> progress<span class="token punctuation">.</span>observability <span class="token keyword">import</span> Observability<span class="token punctuation">,</span> ObservabilityInstruments

Observability<span class="token punctuation">.</span>instrument<span class="token punctuation">(</span>
    app_name<span class="token operator">=</span><span class="token string">"booking-agent"</span><span class="token punctuation">,</span>
    api_key<span class="token operator">=</span>os<span class="token punctuation">.</span>getenv<span class="token punctuation">(</span><span class="token string">"OBSERVABILITY_API_KEY"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    trace_content<span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation">,</span>  <span class="token comment"># Captures tool inputs/outputs and LLM prompts</span>
    instruments<span class="token operator">=</span><span class="token punctuation">{</span>
        ObservabilityInstruments<span class="token punctuation">.</span>OPENAI<span class="token punctuation">,</span>
        ObservabilityInstruments<span class="token punctuation">.</span>LANGCHAIN<span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    additional_tags<span class="token operator">=</span><span class="token punctuation">[</span><span class="token string">"production"</span><span class="token punctuation">,</span> <span class="token string">"v2.1"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span>
</code></pre><p>The foundational setting is <code>trace_content=True</code>, because the rest of the observability workflow depends on having the actual tool inputs and outputs in the trace. Without it, you see that a tool was called and how long it took. You do not see what it returned. For silent failures, the content <em>is</em> the debugging signal. A span that only says &ldquo;search-hotels: 200 OK, 300ms&rdquo; tells you almost nothing about whether the returned data was usable. A span that says &ldquo;search-hotels: 200 OK, 300ms, providers_responded: 1, partial: True&rdquo; tells you everything.</p><h3 id="tools-decorated-for-automatic-span-creation">Tools: Decorated for Automatic Span Creation</h3><pre class=" language-python"><code class="prism  language-python"><span class="token keyword">from</span> progress<span class="token punctuation">.</span>observability <span class="token keyword">import</span> tool

@tool<span class="token punctuation">(</span>name<span class="token operator">=</span><span class="token string">"search-hotels"</span><span class="token punctuation">)</span>
<span class="token keyword">def</span> <span class="token function">search_hotels</span><span class="token punctuation">(</span>city<span class="token punctuation">:</span> <span class="token builtin">str</span><span class="token punctuation">,</span> checkin<span class="token punctuation">:</span> <span class="token builtin">str</span><span class="token punctuation">,</span> checkout<span class="token punctuation">:</span> <span class="token builtin">str</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">&gt;</span> <span class="token builtin">dict</span><span class="token punctuation">:</span>
    <span class="token triple-quoted-string string">"""Calls the hotel provider API."""</span>
    response <span class="token operator">=</span> httpx<span class="token punctuation">.</span>get<span class="token punctuation">(</span>
        <span class="token string">"https://api.hotels.example.com/search"</span><span class="token punctuation">,</span>
        params<span class="token operator">=</span><span class="token punctuation">{</span><span class="token string">"city"</span><span class="token punctuation">:</span> city<span class="token punctuation">,</span> <span class="token string">"checkin"</span><span class="token punctuation">:</span> checkin<span class="token punctuation">,</span> <span class="token string">"checkout"</span><span class="token punctuation">:</span> checkout<span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">)</span>
    <span class="token keyword">return</span> response<span class="token punctuation">.</span>json<span class="token punctuation">(</span><span class="token punctuation">)</span>  <span class="token comment"># May include partial provider metadata</span>
</code></pre><p>The <code>@tool</code> decorator wraps the function in an OpenTelemetry span. It captures the function arguments as span attributes and the return value as span output. When the hotel API returns only one result from one of three providers, that degraded payload is now visible in the trace without any additional logging code.</p><h3 id="the-validation-layer-making-silent-failures-explicit">The Validation Layer: Making Silent Failures Explicit</h3><p>This is the most important pattern in the post. Without it, every span looks successful. With it, you get an explicit signal that something is semantically wrong:</p><pre class=" language-python"><code class="prism  language-python"><span class="token keyword">from</span> progress<span class="token punctuation">.</span>observability <span class="token keyword">import</span> task

@task<span class="token punctuation">(</span>name<span class="token operator">=</span><span class="token string">"validate-results"</span><span class="token punctuation">)</span>
<span class="token keyword">def</span> <span class="token function">validate_results</span><span class="token punctuation">(</span>data<span class="token punctuation">:</span> <span class="token builtin">dict</span><span class="token punctuation">,</span> min_results<span class="token punctuation">:</span> <span class="token builtin">int</span> <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">&gt;</span> <span class="token builtin">dict</span><span class="token punctuation">:</span>
    <span class="token triple-quoted-string string">"""Checks whether a tool response contains meaningful data."""</span>
    results <span class="token operator">=</span> data<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">"results"</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
    metadata <span class="token operator">=</span> data<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">"metadata"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span>
    is_partial <span class="token operator">=</span> metadata<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">"partial"</span><span class="token punctuation">,</span> <span class="token boolean">False</span><span class="token punctuation">)</span>

    issues <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
    <span class="token keyword">if</span> <span class="token builtin">len</span><span class="token punctuation">(</span>results<span class="token punctuation">)</span> <span class="token operator">&lt;</span> min_results<span class="token punctuation">:</span>
        issues<span class="token punctuation">.</span>append<span class="token punctuation">(</span>f<span class="token string">"insufficient_results: got {len(results)}, need {min_results}"</span><span class="token punctuation">)</span>
    <span class="token keyword">if</span> is_partial<span class="token punctuation">:</span>
        issues<span class="token punctuation">.</span>append<span class="token punctuation">(</span>f<span class="token string">"partial_response: {metadata.get('providers_responded')}/{metadata.get('providers_queried')}"</span><span class="token punctuation">)</span>

    <span class="token keyword">return</span> <span class="token punctuation">{</span><span class="token string">"valid"</span><span class="token punctuation">:</span> <span class="token builtin">len</span><span class="token punctuation">(</span>issues<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token string">"issues"</span><span class="token punctuation">:</span> issues<span class="token punctuation">}</span>
</code></pre><p>This function does almost nothing computationally. Its value is purely observational: it creates a span in the trace that says either <code>valid: True</code> or <code>valid: False, issues: ["insufficient_results: got 0, need 1"]</code>. That span becomes a signal you can filter, aggregate and alert on.</p><h3 id="workflow-composition-validate-after-every-external-call">Workflow Composition: Validate After Every External Call</h3><pre class=" language-python"><code class="prism  language-python"><span class="token keyword">from</span> progress<span class="token punctuation">.</span>observability <span class="token keyword">import</span> workflow

@workflow<span class="token punctuation">(</span>name<span class="token operator">=</span><span class="token string">"search-and-book"</span><span class="token punctuation">,</span> version<span class="token operator">=</span><span class="token number">1</span><span class="token punctuation">)</span>
<span class="token keyword">def</span> <span class="token function">search_and_book</span><span class="token punctuation">(</span>destination<span class="token punctuation">:</span> <span class="token builtin">str</span><span class="token punctuation">,</span> checkin<span class="token punctuation">:</span> <span class="token builtin">str</span><span class="token punctuation">,</span> checkout<span class="token punctuation">:</span> <span class="token builtin">str</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">&gt;</span> <span class="token builtin">dict</span><span class="token punctuation">:</span>
    prefs <span class="token operator">=</span> get_preferences<span class="token punctuation">(</span><span class="token string">"user-42"</span><span class="token punctuation">)</span>
    prefs_check <span class="token operator">=</span> validate_preferences<span class="token punctuation">(</span>prefs<span class="token punctuation">)</span>

    hotels <span class="token operator">=</span> search_hotels<span class="token punctuation">(</span>destination<span class="token punctuation">,</span> checkin<span class="token punctuation">,</span> checkout<span class="token punctuation">)</span>
    hotel_check <span class="token operator">=</span> validate_results<span class="token punctuation">(</span>hotels<span class="token punctuation">,</span> min_results<span class="token operator">=</span><span class="token number">2</span><span class="token punctuation">)</span>

    <span class="token keyword">return</span> <span class="token punctuation">{</span>
        <span class="token string">"hotels"</span><span class="token punctuation">:</span> hotels<span class="token punctuation">,</span>
        <span class="token string">"preferences"</span><span class="token punctuation">:</span> prefs<span class="token punctuation">[</span><span class="token string">"preferences"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
        <span class="token string">"all_valid"</span><span class="token punctuation">:</span> hotel_check<span class="token punctuation">[</span><span class="token string">"valid"</span><span class="token punctuation">]</span> <span class="token operator">and</span> prefs_check<span class="token punctuation">[</span><span class="token string">"valid"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span>
</code></pre><p>The pattern is simple: call a tool, then validate. The validation span sits right next to the tool span in the trace tree. When you open a trace where the agent produced a wrong answer, you immediately see which tool returned bad data and whether the validation caught it.</p><h2 id="the-debugging-workflow-from-complaint-to-root-cause">The Debugging Workflow: From Complaint to Root Cause</h2><p>At this point, the agent is instrumented: tool calls are captured, validation spans are recorded and the LLM input/output is visible in the trace. The next question is how an engineer uses that data when something goes wrong in production.</p><p>The workflow below is the reactive debugging path. A user reports a bad result, and the engineer uses the trace to move from symptom to root cause without reproducing the issue, adding temporary logs or redeploying the agent.</p><p>A user reports: &ldquo;The agent recommended Budget Inn as my only hotel option in Barcelona, but I found many better places elsewhere.&rdquo; Here is the exact workflow to diagnose this in under 60 seconds using the <a target="_blank" href="https://www.telerik.com/ai-observability-platform">Progress AI Observability Platform</a>.</p><h3 id="step-1-find-the-trace">Step 1: Find the Trace</h3><p>In the Observations page, filter by app name &ldquo;booking-agent&rdquo; and the time window when the user reported the issue. Each row is one agent execution. Click the one matching the user&rsquo;s session.</p><p>At this point, look for the trace whose timestamp aligns with the complaint and whose high-level metadata matches the affected user flow. The conclusion you want to draw before going deeper is simple: you have the exact execution that produced the bad answer, not just a similar run from the same period.</p><h3 id="step-2-read-the-span-tree">Step 2: Read the Span Tree</h3><p>Here is what the actual trace view shows from a real run of the demo:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/trace-silent-failure-chatopen-ai-span.png?sfvrsn=1f950447_2" alt="Trace view in the Progress Observability Platform showing the ChatOpenAI.chat span with Input/Output. The Input contains partial hotel data (providers_responded: 1, partial: True) and all_valid: False. The Output shows the LLM confidently recommending Budget Inn despite degraded data." /></p><p>The span tree tells the full story right away: 11 spans, zero errors, status &ldquo;Success.&rdquo; Traditional monitoring would see nothing wrong.</p><p>The first thing to check is the mismatch between the overall trace status and the validation signals inside the trace. If the trace says &ldquo;Success&rdquo; but one or more validation spans show <code>valid: False</code>, you are looking at a semantic failure rather than a transport or runtime failure.</p><p>Then click the <code>ChatOpenAI.chat</code> span and look at its Input/Output tab. In this run, the span content can be summarized as:</p><p><strong>Input shown in the span</strong></p><ul><li>Instruction: &ldquo;Summarize hotel options. Be helpful and confident.&rdquo;</li><li>Hotel payload: status <code>200</code>, with 1 result returned.</li><li>Returned hotel: <code>Budget Inn</code>, price <code>$45</code>, rating <code>2.1</code>.</li><li>Provider metadata: <code>providers_queried: 3</code>, <code>providers_responded: 1</code>, <code>partial: True</code>.</li><li>Preferences passed to the model: <code>budget</code>, <code>central</code>, <code>3plus</code>.</li><li>Validation flag: <code>all_valid: False</code>.</li></ul><p><strong>Output shown in the span</strong></p><blockquote><p>For your stay in Barcelona from June 15 to June 20, I found one option that appears to match your budget and location preferences.</p><p>Budget Inn is available for $45 per night and is centrally located.</p><p>Based on the data provided, it looks like the best option for your trip.</p></blockquote><p>The diagnosis is immediate. The hotel search returned only one result from one of three providers (<code>partial: True</code>). The LLM received that degraded data, and <code>all_valid</code> is False. Yet the model confidently recommended Budget Inn, rating 2.1, price $45, because it does not know that two better providers failed to respond.</p><p>The key things to look for are degraded tool output, a failed validation signal and a still-confident final answer. When those three appear together, the conclusion is that the agent did not hallucinate out of nowhere; it made a plausible-sounding recommendation from bad upstream data.</p><h3 id="step-3-determine-scope">Step 3: Determine Scope</h3><p>Scroll through recent traces in the Observations list. Each trace shows span count, latency, token usage and cost at a glance. Open a few neighboring traces from the same time window and check whether their <code>ChatOpenAI.chat</code> inputs also show the same hotel degradation pattern: partial hotel data, too few hotel results or <code>all_valid: False</code>.</p><p>What you are looking for here is repetition: the same degraded hotel pattern across multiple executions, not just the single complaint trace. If multiple recent traces exhibit the same degraded data, the upstream provider has a sustained issue. If only one trace is affected, it was likely a transient timeout.</p><p>The conclusion from this step is whether you are dealing with a one-off bad run or an active incident.</p><h3 id="step-4-verify-the-llms-reasoning">Step 4: Verify the LLM&rsquo;s Reasoning</h3><p>Click the <code>ChatOpenAI.chat</code> span to see exactly what prompt it received. In the Input/Output tab, check whether the model received degraded hotel data, whether <code>all_valid</code> was <code>False</code>, and whether the output still sounded polished and confident anyway. If all three are true, the model behavior is diagnostic rather than mysterious: it was given bad inputs and still optimized for a helpful answer. The root conclusion is that the failure started upstream in the hotel provider path, not in the prompt template or the model itself.</p><p>Total time for this workflow: under a minute. Without traces, this same investigation requires reproducing the issue, adding print statements, redeploying and hoping the intermittent failure happens again. In the real trace above, the entire agent execution took 3.6 seconds and cost $0.0003&mdash;the observability overhead is negligible compared to the diagnostic value.</p><h2 id="moving-from-reactive-to-proactive-detection">Moving from Reactive to Proactive Detection</h2><p>The debugging workflow above is reactive: a user reports a bad answer, and an engineer investigates the trace for that specific execution. That workflow is useful, but it still starts after the user has experienced the failure.</p><p>The next step is to use the same trace data proactively. Instead of waiting for a complaint, you can evaluate incoming traces continuously and watch for quality signals that indicate silent failures: partial hotel results, too few returned options, stale inventory data or confident model responses based on degraded inputs.</p><h3 id="real-time-evaluation-tasks">Real-Time Evaluation Tasks</h3><p>The Progress AI Observability Platform supports LLM-as-a-Judge evaluation tasks that run continuously against incoming traces. Here&rsquo;s how to set one up for our booking agent using the Eval Task Configuration wizard:</p><h4 id="step-1-create-task">Step 1: Create Task</h4><p>Navigate to Evaluation Tasks in the left sidebar and click &ldquo;New Task.&rdquo; Give the task a name, then configure the <strong>Target Data Filter</strong> to scope which spans the evaluator will run against.</p><p>Two filters matter here:</p><ul><li><strong>Application</strong> &ndash; Set to <code>contains "booking"</code> (or the exact app name, e.g., <code>booking-agent</code>). This means the evaluator only runs against traces from your agent, not unrelated services.</li><li><strong>Span Kind</strong> &ndash; set to <code>is "llm_call"</code>. This targets only the LLM call spans, which is where the actual input/output content lives. Evaluating at the <code>llm_call</code> level gives the judge LLM the exact prompt and response, rather than higher-level workflow metadata.</li></ul><p>Before moving to the next step, check the <strong>&ldquo;Show preview of sample matching traces&rdquo;</strong> checkbox. The wizard immediately renders a table of real traces that match your filters&mdash;showing timestamp, application, provider, model, latency, token count, cost and status. If the table looks wrong (wrong app, wrong span kind or empty), fix your filters before proceeding. This preview is the fastest way to confirm your evaluation task will run against the spans you actually intend to evaluate.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/eval-task-configuration.png?sfvrsn=cb7deedb_2" alt="Eval Task Configuration screen showing Target Data Filter with Application " /></p><p>Use the <strong>Run On</strong> toggle to choose between <strong>Real-time Data</strong> (evaluates incoming traces as they arrive) and <strong>Historical Data</strong> (runs against existing traces in a selected time window&mdash;useful for testing the evaluator against known-good and known-bad traces before going live).</p><p>The <strong>Sampling Rate</strong> slider lets you evaluate a percentage of matching traces rather than all of them, which controls judge-model cost at scale.</p><h4 id="step-2-set-up-evaluator">Step 2: Set Up Evaluator</h4><p>The wizard presents three fields:</p><ul><li><strong>Evaluator Name:</strong> <code>booking-quality-check</code></li><li><strong>LLM Integration:</strong> Select your connected LLM (e.g., GPT-4.1-mini via the LLM Integrations page)</li><li><strong>Evaluator Prompt:</strong> Use <code>{{variable_name}}</code> placeholders to inject trace data. For our agent:</li></ul><blockquote><p>You are an expert evaluator assessing whether the input data is valid.</p><br /><p>Your task: Determine whether the input is valid, and provide a brief explanation.</p><br /><p>A [POSITIVE_LABEL, e.g. &ldquo;VALID&rdquo;]:</p><ul><li>The input data contains all_valid property set to True, e.g.,<code>all_valid: True</code></li></ul><p>A [NEGATIVE_LABEL, e.g. &ldquo;INVALID&rdquo;]:</p><ul><li>The input data contains all_valid property set to False, e.g.,<code>all_valid: False</code></li></ul><p>Now evaluate the following:</p><br /><p>Input:<br />{{input}}</p><br /><p>Output:<br />{{output}}</p></blockquote><p>Once you write the prompt, the wizard auto-detects <code>{{input}}</code> and <code>{{output}}</code> as <strong>Prompt Variables</strong> in the right panel. For each variable, configure:</p><ul><li><code>{{input}}</code> &ndash; Source: <strong>Span</strong>. Value: <strong>Input Text</strong> (pulls the input content from the evaluated span).</li><li><code>{{output}}</code> &ndash; Source: <strong>Span</strong>. Value: <strong>Output Text</strong> (pulls the output content from the evaluated span).</li></ul><p>This maps the placeholders to the actual span data captured by <code>trace_content=True</code>. The evaluator LLM receives the exact same input/output you see in the trace&rsquo;s Input/Output tab.</p><ul><li><strong>Score Range Prompt:</strong> Define how the LLM should return its score:</li></ul><pre class=" language-txt"><code class="prism  language-txt">Score is VALID for a POSITIVE_LABEL and INVALID for a NEGATIVE_LABEL.
</code></pre><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/eval-task-prompt.png?sfvrsn=14624f99_2" alt="Eval Task Configuration screen on the Set Up Evaluator step showing the evaluator prompt with {{input}} and {{output}} placeholders, the Prompt Variables mapped to Span Input Text and Output Text, and the Score Range Prompt configured to return VALID or INVALID." /></p><h4 id="step-3-preview--confirm">Step 3: Preview &amp; Confirm</h4><p>The wizard shows a preview of how the evaluator will score a sample trace. Confirm and activate.</p><p>When the hotel API degrades and starts returning partial results, this evaluator&rsquo;s scores drop immediately. You see quality decline in the Scores dashboard before any user complains. Cross-referencing low-scoring traces with their span trees reveals the root cause without manual investigation.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/eval-task-scores.png?sfvrsn=d1a31797_2" alt="Scores view in the Progress Observability Platform showing completed evaluation results with a mix of VALID and INVALID scores for the booking agent evaluator after the task has run on real traces." /></p><p>This is fundamentally different from monitoring error rates. Error rates stay flat during silent failures because nothing throws an exception. Quality scores drop because the output is measurably worse.</p><h3 id="provider-health-inference-from-span-data">Provider Health Inference from Span Data</h3><p>Evaluation tasks tell you when output quality is dropping. The next question is why. Because validation spans are attached to specific tool calls, you can aggregate them over time to identify which dependency is producing degraded data.</p><p>For example, if <code>search-hotels</code> produces <code>valid: False</code> in 5% of traces normally, such as during legitimate low-inventory days or edge-case destinations, but suddenly shows a 35% failure rate in a 15-minute window, the upstream provider is likely degraded.</p><p>Traditional monitoring cannot see this. The API is responding (200 OK, valid JSON). Your infrastructure dashboards show green. Only the content-level validation spans reveal the degradation, because only they know the difference between &ldquo;legitimately no results&rdquo; and &ldquo;provider returned empty because it&rsquo;s broken.&rdquo;</p><p>This pattern requires domain-aware thresholds. A single low-quality hotel result for Barcelona in peak season is almost certainly a failure. A sparse hotel result for a remote destination in the off-season might be legitimate. Encoding these expectations into your validation functions (and therefore into your trace data) is what makes the alerting actionable rather than noisy.</p><h2 id="operational-realities">Operational Realities</h2><h3 id="content-tracing-tradeoffs-storage-cost-and-privacy">Content Tracing Tradeoffs: Storage, Cost and Privacy</h3><p>We have now established that the single most important configuration decision for silent failure detection is enabling content tracing. Without <code>trace_content=True</code>, every span in our booking agent trace looks identical whether the agent worked correctly or failed silently. Both cases show something like <code>search-hotels: 200 OK, 300ms</code>. Only with content do you see the empty result set, the <code>partial: True</code> metadata flag or the stale hotel inventory data that caused the bad answer.</p><p>That visibility is what makes content tracing valuable, but it also introduces operational tradeoffs around storage, billing and privacy. Those tradeoffs are manageable, but they should be addressed deliberately before enabling content capture broadly in production.</p><p>The storage cost is usually modest. In our actual demo trace, the full 11-span trace with content weighs a few KB total. At 1,000 requests per day, that is well under 100 MB of trace data. Under the current span-based billing model, the Progress AI Observability Platform charges per span, not per byte of content, so enabling content tracing does not change billing for this example.</p><p>The privacy tradeoff follows directly from what makes content tracing useful: it captures payloads, not just metadata. Engineers can see the empty result set, stale hotel inventory data, partial response or flawed prompt that caused the agent to fail. But if your agent processes PII in user queries, tool inputs, tool outputs or model prompts, that same data can appear in spans unless it is sanitized, redacted or selectively excluded.</p><p>Treat content tracing as production data capture and apply the same controls you would use for logs or analytics events. In practice, that means sanitizing or redacting sensitive values before they reach traced tool calls, avoiding unnecessary capture of raw user input, and disabling content tracing for specific spans that handle sensitive data. The goal is not to trace everything indiscriminately; it is to capture enough task-relevant content to debug semantic failures without exposing data you do not need.</p><h3 id="the-roi-of-validation-spans">The ROI of Validation Spans</h3><p>Once content tracing is enabled, the next question is whether the extra validation spans are worth the additional observability volume. Each <code>@task</code> validation call adds one span to the trace, so there is a measurable cost to validating every external tool response. The real question is whether that cost is lower than the engineering time and customer impact of discovering silent failures only after users complain.</p><p>Our actual booking agent trace shows the math clearly: <code>11 total spans = 1 agent + 1 workflow + 3 tools + 3 validations + 3 LangChain internal spans</code>.</p><p>In other words, the validation layer accounts for <code>3/11</code> of the observability volume for a fully instrumented request. At <code>1 unit per span</code> on the free tier, <code>20,000 units/month</code> supports roughly <code>1,800</code> fully validated requests per month. The real-world cost of our demo trace was <code>$0.0003</code> in LLM spend and <code>11 observability units</code>.</p><p>Is it worth it? Usually yes.</p><p>Without validation spans, you discover silent failures only when users complain. A single user-reported incident can cost hours of engineering time across reproduction, log review, redeployment and debugging. That is usually more expensive than months of validation span overhead. For agents that make user-facing recommendations or decisions, the economics generally favor always-on validation in production.</p><h3 id="validation-design-principles">Validation Design Principles</h3><p>If validation spans are worth adding, the next question is where to add them and what they should check. Poorly designed validation creates noise; well-designed validation turns domain expectations into useful observability signals.</p><p>Silent failures usually originate at dependency boundaries, especially external APIs. Validate <em>after</em> every external tool call, but not after every internal computation. APIs and other dependencies can return 200 OK with semantically bad data: empty results, partial responses, stale cache entries or misleading metadata. Internal transformations are usually deterministic and easier to test, so validation spans are most valuable where the agent depends on data it does not control.</p><p>Make your &ldquo;valid&rdquo; criteria specific to the tool and the query context. A hotel search returning only one poor option for Barcelona in June is almost certainly a provider failure. A sparse result for a remote destination in the off-season might be legitimate. Encode this domain knowledge in the validation function.</p><p>Do not block the agent on validation failures. The validation layer is observational, not operational. Let the agent continue, let it produce whatever output it produces and use evaluation tasks to alert on quality drops. Blocking on validation turns an observability improvement into a reliability regression (now your agent fails hard where it previously failed silently; that may be worse for some use cases).</p><h2 id="getting-started">Getting Started</h2><p>The debugging approach described in this post requires three things:</p><ol><li><p><strong>Content-aware tracing.</strong> Install <code>progress-observability</code>, call <code>Observability.instrument()</code> with <code>trace_content=True</code>, and decorate your tools with <code>@tool</code>. This gives you the raw visibility.</p></li><li><p><strong>Explicit validation spans.</strong> Add <code>@task</code> validation functions after each external tool call. These turn invisible semantic failures into explicit, filterable signals in your trace data.</p></li><li><p><strong>Continuous quality evaluation.</strong> Set up a real-time evaluation task in the <a target="_blank" href="https://www.telerik.com/ai-observability-platform">Progress Observability Platform</a> that scores your agent&rsquo;s output quality. When scores drop without a corresponding error rate increase, you&rsquo;re likely seeing a silent failure pattern.</p></li></ol><p>The companion demo (<code>silent_failure_demo.py</code>) implements the full booking agent with configurable failure probabilities. Run it 20-30 times against a real Progress AI Observability instance and explore the traces (the free tier at 20,000 units/month is sufficient) . Pay attention to traces where the agent returned &ldquo;success&rdquo; but the validation spans show failures. That gap between reported status and actual quality is where silent failures live.</p><ul><li><a target="_blank" href="https://github.com/NickIliev/silent-failure-demo/">Full source code on GitHub</a></li><li><a target="_blank" href="https://observability.progress.com/documentation">Documentation</a></li><li><a target="_blank" href="https://www.telerik.com/ai-observability-platform">Get started free</a>&nbsp;</li></ul><hr /><h2 id="we-want-your-feedback">We Want Your Feedback</h2><p>Are you running into silent failures in your AI agents? Have questions about instrumenting your specific stack or applying these patterns in production??</p><p>We would love to hear from you. Reach out to our team to discuss your observability or AI production challenges, request a demo or share feedback on this post.</p><ul><li><a target="_blank" href="https://www.telerik.com/contact?preselect=ai-observability-platform">Contact us</a></li></ul>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:9d8a837c-aefd-489a-a053-9009c55405d6</id>
    <title type="text">KendoReact vs. OSS: What You Can Actually Get for Free (and What You Can’t)</title>
    <summary type="text">If you’ve reached the point where free UI libraries are starting to cause friction, it may be time to consider whether they’re still the right choice for your team—and your app.</summary>
    <published>2026-06-17T20:58:55Z</published>
    <updated>2026-06-18T18:00:59Z</updated>
    <author>
      <name>Kathryn Grayson Nanz </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/kendoreact-vs-oss-what-actually-get-free-what-you-cant"/>
    <category term="Kendo UI"/>
    <content type="text"><![CDATA[<p>There&rsquo;s no shortage of React component libraries out there, many of them open-source or free to use. </p><p>Developers often look at a commercial component library and think, &ldquo;Why would I ever pay for something I could get for free from someone else?&rdquo; And it&rsquo;s a fair question to ask! The answer often depends primarily on a) what you&rsquo;re building and b) how much experience you (and your team) have building and maintaining frontend components. </p><p>If you&rsquo;ve reached the point where free UI libraries are starting to cause friction, it may be time to consider whether they&rsquo;re still the right choice for your team&mdash;and your app. </p><p>In this article, we&rsquo;ll break down how to evaluate open-source vs. commercial UI libraries in practical terms, including: </p><ul><li>The right choice for getting started with a new project</li><li>The cost shifts as engineering time, maintenance and integration needs scale up</li><li>Questions about predictability, support and overhead</li><li>How to balance all the pros and cons to make the choice that best suits your needs</li></ul><h2>Who&rsquo;s Building the Library? </h2><p>The number one difference between an open-source (OSS) and commercial library is, of course, who builds and maintains the components. </p><p>Open-source projects are community-built and supported, which has some distinct benefits: most notably, that means you can get involved, personally! If you have a specific dream or vision for improving an open-source project, there are incredible OSS organizers out there who would love you to work with them to make those improvements a reality. It&rsquo;s a chance to both sharpen your skills as a developer and work alongside some truly amazing people. OSS projects are wonderful for identifying a need in the development community and quickly filling that gap. </p><p>On the flip side, the rise of AI and &ldquo;vibe coding&rdquo; is a real emerging challenge in the OSS world right now. Well-intentioned organizers are struggling to sort through low-quality generated issues and code submissions. </p><p>Open-source is also (and always has been) volunteer-based. While some extremely popular OSS projects receive financial backing, the majority are reliant on volunteer contributions. That can make it a challenge for projects to sustain development over long periods of time. </p><p><a href="https://www.telerik.com/kendo-react-ui/components/introduction#what-is-kendoreact">Commercial component libraries</a>, on the other hand, are built by teams of developers who are financially compensated for their time and efforts. This has a few distinct benefits. For one, it means that developers are more likely to stay on the team for long periods of time&mdash;and that institutional knowledge helps create a more stable product. It also means that the library can be the primary focus of the developers on the team, rather than something they contribute to in their spare time on evenings or weekends. </p><h2>How Is the Library Maintained? </h2><p>A library is more than just its first version. As the fields of web development and software engineering evolve, the tools we use have to evolve with it. Even if a tool is perfect when it&rsquo;s created, will it still be useful in six months? What about in a year? Five years? Many React developers still feel the pain of Create React App&rsquo;s slow decline&mdash;and that wasn&rsquo;t even a tiny, niche project! </p><p>This is one of those times when what you&rsquo;re building weighs into the decision-making process heavily. If you&rsquo;re working on a side project of your own, then this matters far less. The same is true if you&rsquo;re prototyping a new idea, testing a new technology or similar. Those are great opportunities to reach for open-source options! </p><p>However, if you&rsquo;re migrating a legacy application, adding new features to an app with a significant userbase or building enterprise software, the longevity and dependability of your tooling matters&mdash;a lot! </p><p>It&rsquo;s also about more than just whether or not the library is still being updated; it&rsquo;s also about <a href="https://www.telerik.com/support/whats-new/kendo-react-ui/roadmap">how often those updates happen</a>, and whether or not there&rsquo;s a regular cadence. If you know that a library will get quarterly updates, you can start planning ahead on your own development cycles with that in mind. It offers the ability to be proactive&mdash;instead of reactive&mdash;when it comes to planning around potentially breaking changes in version upgrades. </p><h2>What Support Is Available? </h2><p>As mentioned earlier, one of the biggest perks of open-source software is the community that surrounds it. Popular projects often have vibrant, engaged groups of developers to help answer questions or write documentation. They also tend to have many resources (like tutorial videos or blogs) written by users, so you can often make a quick Google search and find someone who&rsquo;s tackling a similar problem to the one you&rsquo;re dealing with. </p><p>However, much like maintenance&mdash;can you be sure that the community will be there for the long haul? If you&rsquo;re still using this tool in five years, will there also still be an active chat you can ask questions to &hellip; or will another tool have become more popular in the meantime? </p><p>Additionally, there&rsquo;s also the question of <a href="https://www.telerik.com/kendo-react-ui/support">professional support.</a> Of course, it&rsquo;s handy to be able to Google things and find walk-throughs and tutorials, but what if you need to ask a specific implementation question? Is there someone available to get on a call with you or walk through your individual use case to troubleshoot the issue? If you post on a support forum, are you comfortable with just hoping someone with enough experience will be able to answer your question&mdash;or are you working on a project critical enough that you need to know a dedicated support professional is available if (or perhaps, <em>when</em>) things go wrong? </p><h2>Does It Meet the Required Standards? </h2><p>This is another one that depends a lot on what you&rsquo;re building&mdash;and who you&rsquo;re building it for. If you&rsquo;re building software that needs to meet certain accessibility or security requirements, you may have a harder time verifying that compliance with open-source solutions. </p><p>For example, do you need something that&rsquo;s <a href="https://www.telerik.com/kendo-react-ui/components/security/overview">certified ISO 27001 or SOC 2 compliant?</a> Do you need be able to provide the ACR to show that a given tool <a href="https://www.telerik.com/kendo-react-ui/components/accessibility">meets Section 508 standards</a>? Are you confident that the tools you&rsquo;re using will be updated to meet WCAG 3.0 standards, when that releases? </p><p>While this may not be a concern for every piece of software, when it matters&mdash;it really matters! </p><h2>What Tool Integrations Are Available? </h2><p>Good systems designers know that choosing a tool is about more than the capability of the tool itself. It&rsquo;s about how that tool works with all the other tools in your system&mdash;and the people who have to use it! </p><p>For example, let&rsquo;s talk about design: if you have primarily backend or full stack developers and no designers, it may make sense to choose a component library that comes with <a href="https://www.telerik.com/design-system/docs/themes/get-started/introduction/#available-themes">lots of built in themes</a>&mdash;or even <a href="https://www.telerik.com/design-system/docs/themes/themebuilder/">full theming software!</a> If you&rsquo;re already using CSS tools <a href="https://www.telerik.com/design-system/docs/themes/integrations/tailwind/">Tailwind</a> or <a href="https://www.telerik.com/design-system/docs/themes/kendo-themes/bootstrap/">Bootstrap</a>, you should find a component library that will work well with those options. If you do have designers and they work in Figma, choosing a component library that offers <a href="https://www.telerik.com/design-system/docs/resources/figma-ui-kits/">Figma UI Kits</a> would probably be something that wins you some points with them! </p><p>When you&rsquo;re assessing tools, it&rsquo;s important to keep in mind the big picture&mdash;not just how the tool works in isolation, but how it fits into your entire project and team&rsquo;s workflow. </p><h2>How Does This Work with AI? </h2><p>Similarly, what about AI? If your team is already using VS Code and Copilot, a library that offers <a href="https://www.telerik.com/kendo-react-ui/components/ai-tools">Copilot integrations</a> would be most efficient. If you know you&rsquo;re going to need <a href="https://www.telerik.com/kendo-react-ui/components/ai-components">AI-powered features</a> in your app, finding a component library that&rsquo;s made to support that can save you a lot of time and effort. </p><p>Working with component libraries that don&rsquo;t offer specialized AI tools means that you have to make do with the generic AI output for those components&mdash;which may be OK enough for smaller projects, but can quickly become a stumbling block at scale. If your AI tool only has a basic understanding of things like the library&rsquo;s API, design system or best practices, the code you get back is going to take a lot of revision before it&rsquo;s production ready. By choosing a component library that offers integrated AI tools, you can get back a higher quality caliber of output&mdash;saving you time, tokens and iteration cycles. </p><h2>Which Option Is the Right Fit for Your Team? </h2><p>As you can see, there&rsquo;s a lot to consider when it comes to tooling decisions&mdash;and the decision rarely comes down to cost, alone. Instead, it&rsquo;s about where you want to invest engineering time: building product features or building UI infrastructure. For teams that are already feeling the limits of open-source approaches, the question isn&rsquo;t necessarily &ldquo;why pay for UI components,&rdquo; but rather &ldquo;what is the cost of continuing to build and maintain this ourselves?&rdquo; </p><p>For smaller teams and projects, open-source can be a fantastic solution&mdash;and it comes with the benefit of getting to be an active participant in the ecosystem of the tooling you use. </p><p>For larger teams and enterprise products, the stability, support and extensibility of commercial libraries may make them a better fit. When you find a library that checks all these boxes&mdash;stable maintenance, professional support, compliance certifications and strong ecosystem integrations&mdash;it may be worth investing in. </p><h3>When Open-source UI Libraries Are the Right Fit: </h3><ul><li>You&rsquo;re building a prototype, MVP or internal tool </li><li>Your UI requirements are relatively simple </li><li>Your team is comfortable owning and maintaining UI infrastructure </li><li>You&rsquo;re not worried about long-term scalability, accessibility or compliance concerns </li></ul><h3>When a Commercial UI Library Makes More Sense: </h3><ul><li>You&rsquo;re building a production-grade or enterprise application </li><li>Your UI includes complex components (such as grids, schedulers or data-heavy views) </li><li>Performance, accessibility and consistency are critical </li><li>Your team is losing time integrating, fixing or extending free UI libraries </li><li>You need predictable support and long-term stability </li></ul><p>These tradeoffs are real, and there's no universally right answer. If a commercial library sounds like the right fit, Progress KendoReact is worth a look&mdash;it's designed with exactly these enterprise considerations in mind. It&rsquo;s <a href="https://www.telerik.com/try/kendo-react-ui" target="_blank">free to try</a> for 30 days, including full support to help you get up and running. </p><p>And if that feels like more than you need at this point, there&rsquo;s always <a href="https://www.telerik.com/kendo-react-ui/components/getting-started/free-vs-premium" target="_blank">KendoReact Free</a>: a customizable, enterprise-quality React UI library with no license or sign-up required. Just npm download and start building! Check out the recap below to help figure out which option is the best choice for you. </p><h2>OSS vs. Commercial Libraries at a Glance </h2><table style="margin-right:auto;"><style>table,
 th,
    td {
      border: 1px;
      border-color: #bdbdba;
      border-style: dotted;
      border-collapse: collapse;
      margin-right: auto;
      cellspacing="5";
      text-align: left;
    }
  </style>
 <thead><tr><th data-role="resizable" style="width:33.3333%;padding:5px;"><strong>Factor</strong></th><th data-role="resizable" style="width:33.3333%;padding:5px;"><strong>Open-source Libraries</strong></th><th style="width:33.3333%;padding:5px;"><strong>Commercial UI Libraries (e.g., KendoReact)</strong></th></tr></thead><tbody><tr><td style="width:33.3333%;padding:5px;">Ownership &amp; maintenance</td><td style="width:33.3333%;padding:5px;">Community-driven, variable continuity</td><td style="width:33.3333%;padding:5px;">Dedicated engineering teams, predictable roadmap</td></tr><tr><td style="width:33.3333%;padding:5px;">Support</td><td style="width:33.3333%;padding:5px;">Forums, community, no SLA</td><td style="width:33.3333%;padding:5px;">SLA-backed support from product experts</td></tr><tr><td style="width:33.3333%;padding:5px;">Performance</td><td style="width:33.3333%;padding:5px;">Depends on implementation, often requires customizations for optimization</td><td style="width:33.3333%;padding:5px;">Built-in performance optimizations (e.g. virtualization, large datasets&mdash;grid)</td></tr><tr><td style="width:33.3333%;padding:5px;">Integration</td><td style="width:33.3333%;padding:5px;">Multiple libraries combined, inconsistent APIs</td><td style="width:33.3333%;padding:5px;">Unified component system designed to work together</td></tr><tr><td style="width:33.3333%;padding:5px;">Accessibility &amp; compliance</td><td style="width:33.3333%;padding:5px;">Varies by project, often requires audits and fixes</td><td style="width:33.3333%;padding:5px;">Built-in accessibility aligned with standards</td></tr><tr><td style="width:33.3333%;padding:5px;">Updates and upgrades</td><td style="width:33.3333%;padding:5px;">Irregular, dependent on maintainers</td><td style="width:33.3333%;padding:5px;">Regular releases, documented changes</td></tr><tr><td style="width:33.3333%;padding:5px;">AI tools &amp; compatibility</td><td style="width:33.3333%;padding:5px;">Generic AI output, not component-aware</td><td style="width:33.3333%;padding:5px;">AI tools aligned with real component APIs and usage</td></tr><tr><td style="width:33.3333%;padding:5px;">TCO</td><td style="width:33.3333%;padding:5px;">Low upfront cost, higher long-term engineering effort</td><td style="width:33.3333%;padding:5px;">License cost + reduced engineering, maintenance and integration overhead</td></tr></tbody></table><br /><p><a href="https://www.telerik.com/kendo-react-ui/components/free" target="_blank" class="Btn">Get Started with KendoReact</a></p>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:edd3110a-7b4d-4f43-8db1-b9001b1e73be</id>
    <title type="text">Creating Localized .NET MAUI Applications</title>
    <summary type="text">Reach more people with your apps: Learn how to localize .NET MAUI applications, including XAML text elements, view models and prebuilt Telerik controls for .NET MAUI.</summary>
    <published>2026-06-17T20:17:33Z</published>
    <updated>2026-06-18T18:00:59Z</updated>
    <author>
      <name>Héctor Pérez </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/creating-localized-net-maui-applications"/>
    <content type="text"><![CDATA[<p><span class="featured">Reach more people with your apps: Learn how to localize .NET MAUI applications, including XAML text elements, view models and prebuilt Telerik controls for .NET MAUI.</span></p><p>Making our mobile applications accessible to users who speak a different language than us can make a huge difference in their usage. Fortunately, .NET MAUI offers the power to deliver multi-language applications thanks to the use of resources files.</p><p>Likewise, prebuilt controls such as those from Progress <a href="https://www.telerik.com/maui-ui">Telerik UI for .NET MAUI</a> can be adapted to support localization, allowing you to build and deliver native language experiences quickly. Let&rsquo;s see how to do it!</p><h2 id="understanding-localization-in-.net-maui-apps">Understanding Localization in .NET MAUI Apps</h2><p>Localization is the process of enabling an application to support different languages and cultures; that is, it&rsquo;s not only about translating text strings to other languages, but also adapting date formats, numbers, etc.</p><p>There are some key elements for localizing applications, the first of which are the resource files <strong>.resx</strong>, which are XML files composed of key-value pairs for each language.</p><p>Likewise, we have the class <code>CultureInfo</code>, which represents the user&rsquo;s cultural information. Finally, the class <code>ResourceManager</code> is a class that allows us at runtime to retrieve localized strings according to the language selected on the user&rsquo;s device.</p><h2 id="creating-a-sample-app-to-localize">Creating a Sample App to Localize</h2><p>To show you in a practical way how to integrate localization into your own projects, let&rsquo;s create a test project. To do so, create a new .NET MAUI project without example content, and follow the <a href="https://www.telerik.com/maui-ui/documentation/get-started/first-steps-vs#step-1-set-up-your-net-maui-application">installation guide to add Telerik controls for .NET MAUI</a> to the project.</p><p>Also install the NuGet package <code>CommunityToolkit.Mvvm</code> to create viewmodels cleanly. Once the project has been configured, create a data model to represent a <code>Product</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">Product</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token keyword">string</span> Name <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> Category <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> Price <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 CreatedDate <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><p>Next, let&rsquo;s create a viewmodel that will display information in the UI for a set of fictitious products:</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">MainViewModel</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>Product<span class="token operator">&gt;</span> products <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>ObservableProperty<span class="token punctuation">]</span>
    <span class="token keyword">private</span> DateTime<span class="token operator">?</span> selectedDate<span class="token punctuation">;</span>

    <span class="token keyword">public</span> <span class="token function">MainViewModel</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        SelectedDate <span class="token operator">=</span> DateTime<span class="token punctuation">.</span>Now<span class="token punctuation">;</span>
        <span class="token function">LoadProducts</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">LoadProducts</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        Products <span class="token operator">=</span>
        <span class="token punctuation">[</span>
            <span class="token keyword">new</span> <span class="token class-name">Product</span>
            <span class="token punctuation">{</span>
                Name <span class="token operator">=</span> <span class="token string">"Laptop Pro 15"</span><span class="token punctuation">,</span>
                Category <span class="token operator">=</span> <span class="token string">"Electronics"</span><span class="token punctuation">,</span>
                Price <span class="token operator">=</span> <span class="token number">1299</span><span class="token punctuation">.</span>99m<span class="token punctuation">,</span>
                CreatedDate <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">DateTime</span><span class="token punctuation">(</span><span class="token number">2026</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">15</span><span class="token punctuation">)</span>
            <span class="token punctuation">}</span><span class="token punctuation">,</span>
            <span class="token keyword">new</span> <span class="token class-name">Product</span>
            <span class="token punctuation">{</span>
                Name <span class="token operator">=</span> <span class="token string">"Running Shoes"</span><span class="token punctuation">,</span>
                Category <span class="token operator">=</span> <span class="token string">"Clothing"</span><span class="token punctuation">,</span>
                Price <span class="token operator">=</span> <span class="token number">89</span><span class="token punctuation">.</span>95m<span class="token punctuation">,</span>
                CreatedDate <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">DateTime</span><span class="token punctuation">(</span><span class="token number">2026</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">20</span><span class="token punctuation">)</span>
            <span class="token punctuation">}</span><span class="token punctuation">,</span>
            <span class="token keyword">new</span> <span class="token class-name">Product</span>
            <span class="token punctuation">{</span>
                Name <span class="token operator">=</span> <span class="token string">"Organic Coffee Beans"</span><span class="token punctuation">,</span>
                Category <span class="token operator">=</span> <span class="token string">"Food"</span><span class="token punctuation">,</span>
                Price <span class="token operator">=</span> <span class="token number">24</span><span class="token punctuation">.</span>50m<span class="token punctuation">,</span>
                CreatedDate <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">DateTime</span><span class="token punctuation">(</span><span class="token number">2026</span><span class="token punctuation">,</span> <span class="token number">3</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 keyword">new</span> <span class="token class-name">Product</span>
            <span class="token punctuation">{</span>
                Name <span class="token operator">=</span> <span class="token string">"Wireless Mouse"</span><span class="token punctuation">,</span>
                Category <span class="token operator">=</span> <span class="token string">"Electronics"</span><span class="token punctuation">,</span>
                Price <span class="token operator">=</span> <span class="token number">45</span><span class="token punctuation">.</span>00m<span class="token punctuation">,</span>
                CreatedDate <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">DateTime</span><span class="token punctuation">(</span><span class="token number">2025</span><span class="token punctuation">,</span> <span class="token number">12</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">)</span>
            <span class="token punctuation">}</span><span class="token punctuation">,</span>
            <span class="token keyword">new</span> <span class="token class-name">Product</span>
            <span class="token punctuation">{</span>
                Name <span class="token operator">=</span> <span class="token string">"Winter Jacket"</span><span class="token punctuation">,</span>
                Category <span class="token operator">=</span> <span class="token string">"Clothing"</span><span class="token punctuation">,</span>
                Price <span class="token operator">=</span> <span class="token number">159</span><span class="token punctuation">.</span>99m<span class="token punctuation">,</span>
                CreatedDate <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">DateTime</span><span class="token punctuation">(</span><span class="token number">2026</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">28</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 code above, you can notice that hard-coded strings in English are used to display information to users only in that language. Now, let&rsquo;s create an example UI, using a <a href="https://www.telerik.com/maui-ui/datepicker">.NET MAUI RadDatePicker</a> (which is an enhanced DatePicker) and a <a href="https://www.telerik.com/maui-ui/datagrid">MAUI RadDataGrid</a> to easily display data tables:</p><pre class=" language-xml"><code class="prism  language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Grid</span>
 <span class="token attr-name">Padding</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>16<span class="token punctuation">"</span></span>
 <span class="token attr-name">RowDefinitions</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Auto,Auto,*<span class="token punctuation">"</span></span>
 <span class="token attr-name">RowSpacing</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>12<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
 <span class="token comment">&lt;!--  Welcome message  --&gt;</span>
 <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Label</span>
     <span class="token attr-name">FontAttributes</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Bold<span class="token punctuation">"</span></span>
     <span class="token attr-name">FontSize</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>20<span class="token punctuation">"</span></span>
     <span class="token attr-name">HorizontalOptions</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Center<span class="token punctuation">"</span></span>
     <span class="token attr-name">Text</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Welcome to the app<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
 <span class="token comment">&lt;!--  Date picker  --&gt;</span>
 <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>HorizontalStackLayout</span>
     <span class="token attr-name">Grid.Row</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>1<span class="token punctuation">"</span></span>
     <span class="token attr-name">HorizontalOptions</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Center<span class="token punctuation">"</span></span>
     <span class="token attr-name">Spacing</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>10<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>Label</span>
         <span class="token attr-name">FontSize</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>16<span class="token punctuation">"</span></span>
         <span class="token attr-name">Text</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Created Date<span class="token punctuation">"</span></span>
         <span class="token attr-name">VerticalOptions</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>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><span class="token namespace">telerik:</span>RadDatePicker</span>
         <span class="token attr-name">Date</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>{Binding SelectedDate}<span class="token punctuation">"</span></span>         
         <span class="token attr-name">WidthRequest</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>250<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>HorizontalStackLayout</span><span class="token punctuation">&gt;</span></span>
 <span class="token comment">&lt;!--  Product grid  --&gt;</span>
 <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">telerik:</span>RadDataGrid</span>
     <span class="token attr-name">Grid.Row</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>2<span class="token punctuation">"</span></span>
     <span class="token attr-name">AutoGenerateColumns</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>False<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 Products}<span class="token punctuation">"</span></span>
     <span class="token attr-name">UserFilterMode</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Auto<span class="token punctuation">"</span></span>
     <span class="token attr-name">UserGroupMode</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Auto<span class="token punctuation">"</span></span>
     <span class="token attr-name">UserSortMode</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Auto<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>RadDataGrid.Columns</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>DataGridTextColumn</span> <span class="token attr-name">HeaderText</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Product Name<span class="token punctuation">"</span></span> <span class="token attr-name">PropertyName</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Name<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>DataGridTextColumn</span> <span class="token attr-name">HeaderText</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Category<span class="token punctuation">"</span></span> <span class="token attr-name">PropertyName</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Category<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>DataGridNumericalColumn</span>
             <span class="token attr-name">CellContentFormat</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>{}{0:C}<span class="token punctuation">"</span></span>
             <span class="token attr-name">HeaderText</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Price<span class="token punctuation">"</span></span>
             <span class="token attr-name">PropertyName</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Price<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>DataGridDateColumn</span>
             <span class="token attr-name">CellContentFormat</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>{}{0:d}<span class="token punctuation">"</span></span>
             <span class="token attr-name">HeaderText</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Created Date<span class="token punctuation">"</span></span>
             <span class="token attr-name">PropertyName</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>CreatedDate<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>RadDataGrid.Columns</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>RadDataGrid</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>Grid</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>Don&rsquo;t forget to register both <code>MainPage</code> and <code>MainPageViewModel</code> in the dependency container:</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>MainPage<span class="token punctuation">&gt;</span></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>MainViewModel<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>Finally, let&rsquo;s inject the view model into the page&rsquo;s code-behind, while assigning the injected reference to <code>BindingContext</code>:</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">MainPage</span> <span class="token punctuation">:</span> ContentPage
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token function">MainPage</span><span class="token punctuation">(</span>MainViewModel 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>
<span class="token punctuation">}</span>
</code></pre><p>After implementing the above changes, we will have a UI like the following:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/base-app-product-grid-english.png?sfvrsn=287ffbd9_2" alt="Product grid in base app with English labels" /></p><p>As we anticipated, all the UI content is in English. Some text chunks we might want to internationalize could be the page title, the welcome message, column headers, product categories, etc. Let&rsquo;s see how to achieve it.</p><h2 id="creating-resource-files-to-localize-a-.net-maui-app">Creating Resource Files to Localize a .NET MAUI App</h2><p>Localization of .NET applications is based on resource files. To create these files, you must start by creating a file with the extension <code>.resx</code>, which will contain the strings in the original language.</p><p>In our example, we are going to create a new folder <code>Resources/Strings</code>, and inside create a file named <code>AppStrings.resx</code>. You can achieve this using the context menu on the created folder, selecting <strong>Add</strong> | <strong>New Item</strong>. Then, in the search box enter the term <strong>resources</strong>, which will filter the template for the resource file:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/selecting-resource-template.png?sfvrsn=943723e7_2" alt="Selecting the appropriate template for a resource file" /></p><p>Once you have created the file, you can open it and start adding the application&rsquo;s strings in their original language, using the <strong>+</strong> button. This will open a new window where you can enter information such as the string key, the data type, the value and optionally comments:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/adding-localization-string.png?sfvrsn=68f8fbaf_2" alt="Developer adding a new localization string in editor" /></p><p>Another way to add strings more quickly is to paste them in the format: <strong>Key name</strong> + <strong>tab</strong> + <strong>value</strong>. For example, you can copy and paste the following content, and paste it directly into the resource editor, which will create a series of rows in the file:</p><pre class=" language-text"><code class="prism  language-text">LanguageLabelLanguage
ProductNameProduct Name
CategoryCategory
PricePrice
CreatedDateCreated Date
SelectLanguageSelect a language
ElectronicsElectronics
ClothingClothing
FoodFood
FilterProductsFilter Products
WelcomeWelcome to the localized app
</code></pre><p>After having the base file, the next step is to create an additional resource file for each translation you want to perform. For the example, I will create a new file in <code>Resources/Strings</code> called <code>AppStrings.es.resx</code>. You can notice that each new file must be named the same as the main one, adding the ISO code of the language you want to translate to.</p><p>When you open the file, you will see that a new column has been added, where you can enter the information of the file, as in the following example:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/resx-translation-strings.png?sfvrsn=9ced7f57_2" alt="Resx files showing original and translated string values" /></p><p>With the resource files created, it is time to connect the strings with the UI.</p><h2 id="localizing-elements-in-the-ui">Localizing Elements in the UI</h2><p>With the strings correctly localized, let&rsquo;s go to the UI page. There, you need to add a namespace for the path where the localized files are located, similar to this:</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>resx</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>clr-namespace:MauiLocalizedApps.Resources.Strings<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
</code></pre><p>Then, on each element you want to localize you must use the markup extension <code>x:Static</code>, using the key of the resource you want to replace. This is an example of the Label control corresponding to the localized welcome message:</p><pre class=" language-xml"><code class="prism  language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Label</span>
<span class="token attr-name">...</span>
    <span class="token attr-name">Text</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>{x:Static resx:AppStrings.Welcome}<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
</code></pre><p>Other strings we will replace will be the creation date and the welcome one, as shown 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>ContentPage</span> <span class="token attr-name">...</span>
    <span class="token attr-name">Title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>{x:Static resx:AppStrings.AppTitle}<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>Grid</span>
     <span class="token attr-name">Padding</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>16<span class="token punctuation">"</span></span>
     <span class="token attr-name">RowDefinitions</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Auto,Auto,*<span class="token punctuation">"</span></span>
     <span class="token attr-name">RowSpacing</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>12<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
     <span class="token comment">&lt;!--  Welcome message  --&gt;</span>
     <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Label</span>
         <span class="token attr-name">FontAttributes</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Bold<span class="token punctuation">"</span></span>
         <span class="token attr-name">FontSize</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>20<span class="token punctuation">"</span></span>
         <span class="token attr-name">HorizontalOptions</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Center<span class="token punctuation">"</span></span>
         <span class="token attr-name">Text</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>{x:Static resx:AppStrings.Welcome}<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
     <span class="token comment">&lt;!--  Date picker  --&gt;</span>
     <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>HorizontalStackLayout</span>
         <span class="token attr-name">Grid.Row</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>1<span class="token punctuation">"</span></span>
         <span class="token attr-name">HorizontalOptions</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Center<span class="token punctuation">"</span></span>
         <span class="token attr-name">Spacing</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>10<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>Label</span>
             <span class="token attr-name">FontSize</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>16<span class="token punctuation">"</span></span>
             <span class="token attr-name">Text</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>{x:Static resx:AppStrings.CreatedDate}<span class="token punctuation">"</span></span>
             <span class="token attr-name">VerticalOptions</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>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><span class="token namespace">telerik:</span>RadDatePicker</span> <span class="token attr-name">Date</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>{Binding SelectedDate}<span class="token punctuation">"</span></span> <span class="token attr-name">WidthRequest</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>250<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>HorizontalStackLayout</span><span class="token punctuation">&gt;</span></span>
     ...
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>Grid</span><span class="token punctuation">&gt;</span></span>     
</code></pre><p>After applying the changes and changing the device language to Spanish, we get the following result:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/xaml-spanish-localized-strings.png?sfvrsn=7b00648d_2" alt="Screenshot of XAML file with Spanish localized strings" /></p><p>Now, let&rsquo;s see how to localize the view model strings.</p><h2 id="localizing-data-strings-from-the-viewmodel">Localizing Data Strings from the viewmodel</h2><p>In addition to localizing elements in the XAML file, we can also load translated strings that are found in the view model. For example, suppose we want to translate the names of the categories shown in the DataGrid.</p><p>To achieve this, we can use the static class that is created when we create a resource file, which takes the same name as the created file. In our case it is called <code>AppStrings</code>, and we will use it as follows:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">LoadProducts</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    Products <span class="token operator">=</span>
    <span class="token punctuation">[</span>
        <span class="token keyword">new</span> <span class="token class-name">Product</span>
        <span class="token punctuation">{</span>
            <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
            Category <span class="token operator">=</span> AppStrings<span class="token punctuation">.</span>Electronics
        <span class="token punctuation">}</span><span class="token punctuation">,</span>
        <span class="token keyword">new</span> <span class="token class-name">Product</span>
        <span class="token punctuation">{</span>
            <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
            Category <span class="token operator">=</span> AppStrings<span class="token punctuation">.</span>Clothing<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>With the previous changes, we have seen how to display translated strings to the user found in XAML files and view models:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/localized-viewmodel-text.png?sfvrsn=f3f493dc_2" alt="Localized text rendered from view models in interface" /></p><p>However, we still have strings in the graphical controls that have not been localized. Let&rsquo;s see how to translate them.</p><h2 id="localizing-internal-strings-of-telerik-controls-for-.net-maui">Localizing Internal Strings of Telerik Controls for .NET MAUI</h2><p>In case you also want to localize the strings of Telerik controls, such as those that show options in the DataGrid, you can see all the keys used in the controls on the <a href="https://www.telerik.com/maui-ui/documentation/globalization-localization">.NET MAUI globalization and localization</a> page. On that page you will find links for each control and their respective strings.</p><p>For example, to localize the .NET MAUI DataGrid, we must copy a series of keys that start with <code>DataGrid_...</code>, such as <code>DataGrid_DistinctValues_SelectAll</code>, <code>DataGrid_Filter_ApplyFilter</code>, <code>DataGrid_Filter_ResetFilter</code>, etc. These are the keys that I will add to the file <code>AppStrings.resx</code>.</p><p>After doing this, you must create a class that inherits from <code>TelerikLocalizationManager</code>, like the one shown below:</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">CustomLocalizationManager</span> <span class="token punctuation">:</span> TelerikLocalizationManager
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token keyword">override</span> <span class="token keyword">string</span> <span class="token function">GetString</span><span class="token punctuation">(</span><span class="token keyword">string</span> key<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">string</span><span class="token operator">?</span> localizedValue <span class="token operator">=</span> AppStrings<span class="token punctuation">.</span>ResourceManager<span class="token punctuation">.</span><span class="token function">GetString</span><span class="token punctuation">(</span>
            key<span class="token punctuation">,</span> CultureInfo<span class="token punctuation">.</span>CurrentUICulture<span class="token punctuation">)</span><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>localizedValue<span class="token punctuation">)</span><span class="token punctuation">)</span>
        <span class="token punctuation">{</span>
            <span class="token keyword">return</span> localizedValue<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">GetString</span><span class="token punctuation">(</span>key<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 use the <code>GetString</code> method to attempt to retrieve a key and its translated value in the device&rsquo;s language. If a translated string is found it is returned; otherwise the original value is returned.</p><p>Finally, you must register the created manager in <code>MauiProgram.cs</code> as follows:</p><pre class=" language-csharp"><code class="prism  language-csharp"><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>
    TelerikLocalizationManager<span class="token punctuation">.</span>Manager <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">CustomLocalizationManager</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>
</code></pre><p>When running the app, we will get the following result:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/telerik-datagrid-spanish-strings.gif?sfvrsn=cd4718ff_2" alt="Telerik DataGrid displaying Spanish localization strings" /></p><p>In the image above you can see how the DataGrid options are displayed translated into Spanish. With this, we have finished localizing the strings in our application.</p><h2 id="conclusion">Conclusion</h2><p>Throughout this article you&rsquo;ve seen how to localize .NET MAUI applications. You&rsquo;ve seen how to translate text elements found in XAML files, view models and even prebuilt Telerik controls for .NET MAUI. Now it&rsquo;s your time to reach more people with your apps by using localization through resource files.</p><blockquote><p>Try out Telerik UI for .NET MAUI free for 30 days.</p><p>&nbsp;</p><p><a target="_blank" href="https://www.telerik.com/try/ui-for-maui" class="Btn">Try Now</a></p></blockquote>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:3f695ccd-2fff-4d8d-af98-598fe8161769</id>
    <title type="text">Building Component-Aware Production UIs with Angular and Kendo UI MCP</title>
    <summary type="text">Through MCP, we can use component-aware AI to help us build user interfaces in conjunction with our favorite component libraries. See it in Angular!</summary>
    <published>2026-06-15T16:26:20Z</published>
    <updated>2026-06-18T18:00:59Z</updated>
    <author>
      <name>Dany Paredes </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/building-component-aware-production-ui-angular-kendo-ui-mcp"/>
    <content type="text"><![CDATA[<p><span class="featured">Through MCP, we can use component-aware AI to help us build user interfaces in conjunction with our favorite component libraries. See it in Angular!</span></p><p>We as developers use AI tools like Cursor, Claude Code and Copilot; they have changed how we write code. We delegate entire sections of our applications to an agent, and for many tasks, this works really well.</p><p>But when it comes to building user interfaces with a specific framework, things get complicated fast. Results are unpredictable, the code feels incomplete and we end up spending more time fixing than building.</p><p>If you have felt that, you are not alone. The good news is that the problem is not the AI. The problem is how we are using it.</p><p>We will see why AI struggles with UI development and how a new approach called &ldquo;component-aware AI&rdquo; changes everything. But first, let&rsquo;s understand what is really missing.</p><h2 id="the-problem">The Problem</h2><p>In the world of AI, we read about context all the time, which is the key problem when things go wrong. When you ask your AI assistant to use a component from a specific framework, it relies on its training data. It does not know:</p><ul><li>The exact version of Angular you are using</li><li>The best practices for your library</li><li>The specific design rules of your organization</li></ul><blockquote><p>Do you want to learn more about the <a href="https://www.progress.com/resources/webinars/the-context-problem-behind-stalled-ai-roi">context problem</a>?</p></blockquote><p>So the AI starts guessing. And that guessing creates real problems:</p><ul><li><strong>High costs and effort:</strong> We waste many tokens just explaining our UI library to the AI. We end up copying and pasting manuals, providing code examples and fixing mistakes, making the whole process slow and expensive.</li><li><strong>The &ldquo;fix and repeat&rdquo; loop:</strong> We copy the code, find a small bug, ask the AI to fix it and repeat the cycle.</li><li><strong>Low-quality code:</strong> The code the AI gives us is often incomplete or outdated. We spend more time fixing it than it would have taken to write it from scratch.</li></ul><p>But we have good news: using a new approach called &ldquo;component-aware AI&rdquo; changes everything.</p><h2 id="the-shift-to-mcp">The Shift to MCP</h2><p>We need a new way for the AI to work. We need &ldquo;component-aware AI.&rdquo; This shift is made possible by the <a href="https://www.telerik.com/blogs/promise-model-context-protocol">Model Context Protocol (MCP)</a>. Instead of the AI trying to remember code from its training data, it can read the live documentation of your library in real time.</p><p>Think of it like the difference between following a recipe from memory versus using a smart kitchen robot with everything preconfigured. It is automated, precise and much freer from errors.</p><p>When you use an MCP like the Progress Kendo UI for <a href="https://www.telerik.com/kendo-angular-ui/components/ai-tools/agentic-ui-generator/getting-started">Angular MCP</a>, the AI assistant queries the official library data before it writes any code. This verifies that every property and every method is correct.</p><p>The best way to understand the difference is to see it in action. I have experienced the shift from working with blind AI to giving agents the right tools to deliver code with confidence and without technical debt. Let&rsquo;s do it!</p><blockquote><p>Want to build your own MCP server? Check this out: <a href="https://www.youtube.com/watch?v=mM8T8bSCTyk">https://www.youtube.com/watch?v=mM8T8bSCTyk</a></p></blockquote><h2 id="the-traditional-way-the-blind-ai">The Traditional Way (The &ldquo;Blind&rdquo; AI)</h2><p>Let&rsquo;s start with the scenario you probably already know.</p><p>Open your terminal and run these two commands to create the project and install the Kendo UI for <a href="https://www.telerik.com/kendo-angular-ui/components/grid">Angular Grid</a>:</p><pre class=" language-bash"><code class="prism  language-bash"><span class="token comment"># Create the project</span>
ng new accounting-app --style<span class="token operator">=</span>scss --ssr<span class="token operator">=</span>false
 
<span class="token comment"># Install Kendo UI Grid</span>
ng add @progress/kendo-angular-grid
</code></pre><p>Then, you ask your AI assistant (without specialized context) to build the interface:</p><blockquote><p><strong>Prompt:</strong> &ldquo;Create a General Ledger grid with Kendo UI for Angular. Show Date, Description, Debit, and Credit. Add footer totals.&rdquo;</p></blockquote><p>The project compiles and runs, but the result is full of immediate technical debt. Here is what the AI generates:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token comment">// ❌ BLIND AI: Ignores Kendo built-in aggregation utilities</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">App</span> <span class="token punctuation">{</span>
 <span class="token keyword">protected</span> readonly ledgerItems<span class="token punctuation">:</span> LedgerEntry<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token operator">...</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
 
 <span class="token comment">// Manual logic the developer must maintain forever</span>
 <span class="token keyword">protected</span> <span class="token keyword">get</span> <span class="token function">totalDebit</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">number</span> <span class="token punctuation">{</span>
   <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>ledgerItems<span class="token punctuation">.</span><span class="token function">reduce</span><span class="token punctuation">(</span><span class="token punctuation">(</span>sum<span class="token punctuation">,</span> item<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> sum <span class="token operator">+</span> item<span class="token punctuation">.</span>debit<span class="token punctuation">,</span> <span class="token number">0</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">get</span> <span class="token function">totalCredit</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">number</span> <span class="token punctuation">{</span>
   <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>ledgerItems<span class="token punctuation">.</span><span class="token function">reduce</span><span class="token punctuation">(</span><span class="token punctuation">(</span>sum<span class="token punctuation">,</span> item<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> sum <span class="token operator">+</span> item<span class="token punctuation">.</span>credit<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
 <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Let&rsquo;s look at the problems here. For <code>totalDebit</code>, if you add more columns with totals, you have to manually write a <code>.reduce()</code> for each one. The AI does not know that Kendo UI already has <a href="https://www.telerik.com/kendo-angular-ui/components/grid/grouping/aggregates"><code>aggregateBy</code></a> built in.</p><p>Another common inconsistency is with colors. Instead of using design tokens, it uses hard-coded values like <code>#f5f7fb</code>, which breaks visual coherence across the app.</p><p>The worst part is the lack of accessibility. There are no ARIA attributes or semantic roles, which Kendo UI handles automatically when configured correctly.</p><pre class=" language-html"><code class="prism  language-html"><span class="token comment">&lt;!-- ❌ BLIND AI: Hardcoded color, no accessibility, no built-in features --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid</span> <span class="token attr-name">[data]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>ledgerItems<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>kendo-grid-column</span> <span class="token attr-name">color</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>#f5f7fb<span class="token punctuation">"</span></span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>debit<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Debit<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>kendo-grid-column</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>In the end, you spend more time cleaning up AI code than you would have spent writing it from scratch. The real issue is that we are not giving the AI the right tools to perform at its best.</p><p>We can fix that, because the solution is simpler than you might think.</p><h2 id="the-agentic-way-using-the-power-of-kendo-ui-mcp">The Agentic Way: Using the Power of Kendo UI MCP</h2><p>Now, let&rsquo;s move to the productive workflow and combine the power of Kendo UI MCP with our agents.</p><p>Instead of the manual configuration and the &ldquo;blind&rdquo; approach, we start with a single command. Open your terminal and run:</p><pre class=" language-bash"><code class="prism  language-bash">npx kendo angular setup
</code></pre><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/npx-kendo-angular-set.png?sfvrsn=58d383f_2" alt="Git npx kendo angular setup" /></p><p>Here is where the magic happens. It automatically installs the license and registers the <strong>Kendo UI MCP Server</strong> across your IDEs and AI agents (VS Code, Cursor, Claude Code). From this point on, your agent has full context and no longer has to guess.</p><p>Now we ask the same AI agent to build the same application with the same prompt. This time our agent, thanks to MCP, uses the <code>Kendo UI Generator</code> tool to generate the code.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/angular-mcp-grid.png?sfvrsn=8457b8db_2" alt="Create a general ledger grid with Kendo UI for Angular. Show date, description, debit, and credit. Add footer totals." /></p><p>Let&rsquo;s look at the generated code and why it is production-ready.</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token comment">// ✅ COMPONENT-AWARE AI: Uses Signals, Modern Angular Patterns, and Kendo's aggregateBy</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> aggregateBy<span class="token punctuation">,</span> AggregateResult <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@progress/kendo-data-query'</span><span class="token punctuation">;</span>
 
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">GeneralLedgerComponent</span> <span class="token punctuation">{</span>
 <span class="token keyword">private</span> ledgerService <span class="token operator">=</span> <span class="token function">inject</span><span class="token punctuation">(</span>LedgerService<span class="token punctuation">)</span><span class="token punctuation">;</span>
 
 <span class="token comment">// Modern signal-based state</span>
 <span class="token keyword">protected</span> ledgerEntries <span class="token operator">=</span> signal<span class="token operator">&lt;</span>LedgerEntry<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>ledgerService<span class="token punctuation">.</span><span class="token function">getLedgerEntries</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
 
 <span class="token comment">// Kendo's aggregateBy handles all column totals in one call, no manual .reduce() needed</span>
 <span class="token keyword">protected</span> totals <span class="token operator">=</span> computed<span class="token operator">&lt;</span>AggregateResult<span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span>
   <span class="token function">aggregateBy</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">ledgerEntries</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> field<span class="token punctuation">:</span> <span class="token string">'debit'</span><span class="token punctuation">,</span> aggregate<span class="token punctuation">:</span> <span class="token string">'sum'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
     <span class="token punctuation">{</span> field<span class="token punctuation">:</span> <span class="token string">'credit'</span><span class="token punctuation">,</span> aggregate<span class="token punctuation">:</span> <span class="token string">'sum'</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>The AI now uses Signals (<code>signal</code>, <code>computed</code>) and proper dependency injection (<code>inject</code>), which are the recommended modern Angular patterns. And instead of a manual <code>.reduce()</code> per column, it uses Kendo UI <code>aggregateBy</code>. Add a new column with a total, and you just add one line to the array, nothing else.</p><p>In the SCSS file, instead of hard-coding colors, it uses official Kendo UI Design Tokens such as <code>var(--kendo-color-surface-alt)</code> and <code>var(--kendo-color-success)</code>.</p><p>And in the template, the difference is clear:</p><pre class=" language-html"><code class="prism  language-html"><span class="token comment">&lt;!-- ✅ COMPONENT-AWARE AI: Design tokens, accessibility, full features enabled --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid</span>
   <span class="token attr-name">[data]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>ledgerEntries()<span class="token punctuation">"</span></span>
   <span class="token attr-name">[reorderable]</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 attr-name">[navigable]</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 attr-name">role</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>table<span class="token punctuation">"</span></span>
   <span class="token attr-name">aria-label</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>General Ledger Entries<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>kendo-grid</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>That is the key difference. When we use <a href="https://www.telerik.com/kendo-angular-ui/components/ai-tools">Kendo UI for Angular MCP</a>, our agents do not just generate code. They generate the right code, follow best practices and deliver to a high standard.</p><p>Here are the three benefits I value most:</p><ol><li>Significantly reduced hallucinations: The AI does not guess property names. It reads the actual documentation in real time.</li><li>Accessible by default: Kendo UI components are built to follow accessibility standards. When the AI uses these components correctly, your app is automatically inclusive.</li><li>Token efficiency: No more copying large lines of documentation into your chat. The AI fetches only what it needs, saving you tokens and money.</li></ol><blockquote><p>Go deeper and explore <a href="https://www.telerik.com/kendo-angular-ui/components/ai-tools#kendo-ui-for-angular-ai-tools-overview">everything that Kendo UI MCP can do</a>.</p></blockquote><p>Now that we have seen both worlds side by side, let&rsquo;s talk about what this really means for how we choose our tools going forward.</p><h2 id="conclusion">Conclusion</h2><p>Simply using AI is not enough. And simply using a component library is not enough anymore, either.</p><p>This is the paradigm shift. We are moving from a world where a library just gives you components to one where a library also provides the context layer that makes modern development possible. Kendo UI MCP is exactly that: it acts as a bridge between your agents and the library so that the AI can do its job properly rather than guessing.</p><p>By bridging that context gap, we move from &ldquo;guessing&rdquo; to &ldquo;knowing&rdquo; and agents stop hallucinating APIs and start delivering production-ready code.</p><blockquote><p>My personal take: Next time you evaluate a UI library, do not just look at the component catalog. Ask if your framework has the tools for the way we build software today. Because that is the question I ask myself now, every time.</p></blockquote><blockquote><p>BTW, if you want to work less and ship more, it is time to <a href="https://www.telerik.com/blogs/angular-kendo-ui-mcp-making-agents-work">make your agents work for you</a> so you can focus on building what really matters.</p></blockquote><h3 id="resources">Resources</h3><ul><li><a href="https://www.telerik.com/kendo-angular-ui"><strong>Kendo UI for Angular</strong></a></li><li><strong>MCP Protocol:</strong> <a href="https://modelcontextprotocol.io/">Learn more about MCP</a></li></ul><h2 id="try-now">Try Now</h2><p>Remember, Kendo UI for Angular comes with a free 30-day trial, so you can explore all these goodies yourself.</p><p><a href="https://www.telerik.com/try/kendo-angular-ui" class="Btn">Try Now</a></p>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:49c497b3-3ecf-4392-b05b-8f6a9712b329</id>
    <title type="text">Telerik UI for Blazor Meets A2UI: The Next Step Toward Dynamic UI Generation</title>
    <summary type="text">Learn how A2UI enables AI systems to generate interactive, dynamically generated Blazor interfaces and how Progress Telerik UI for Blazor will help.</summary>
    <published>2026-06-15T13:30:53Z</published>
    <updated>2026-06-18T18:00:59Z</updated>
    <author>
      <name>Maya Mateva </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/telerik-ui-blazor-meets-a2ui-next-step-toward-dynamic-ui-generation"/>
    <content type="text"><![CDATA[<p><span class="featured">Learn how A2UI enables AI systems to generate interactive, dynamically generated Blazor interfaces and how Progress Telerik UI for Blazor will help.</span></p><p>Imagine this: instead of clicking through a travel app&mdash;picking dates, applying filters, scrolling results, opening tabs to compare&mdash;you type one sentence:</p><p><em>&ldquo;I want to get a flight from London to New York. Give me a plan as a dashboard with important data about the flight.&rdquo;</em></p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/blazor-a2ui-flight-dashboard.gif?sfvrsn=9f6fca6_2" alt="Telerik UI for Blazor A2UI produced a flight assistant dashboard that asks origin, destination, departure and return dates, then shows flight overviews, airport information, travel tips, flight prices in a bar chart, flight price data in table by month" /></p><p>What comes back is more than a paragraph explaining the basics. It&rsquo;s a whole interface with a structured results grid showing airline, price, duration and departure time. It&rsquo;s a comparison view so you can weigh your options, all assembled on the spot. No predefined screen needed. Just intent and a UI that responds to it.</p><p>That is the experience shift A2UI creates. And it is coming to <a href="https://www.telerik.com/blazor-ui">Progress Telerik UI for Blazor</a>.</p><p>Don&rsquo;t miss the live demo! <a href="https://www.telerik.com/campaigns/telerik-and-kendo-ui-2026-q2-release-webinar">Register for the Telerik and Kendo UI 2026 Q2 release webinar on June 16</a> to see A2UI transforming intent into interactive Blazor UI in real time.</p><h2 id="what-is-a2ui">What Is A2UI?</h2><p><a href="https://a2ui.org/specification/v0.8-a2ui/">A2UI (Agent-to-User Interface)</a> is an open protocol introduced by Google that defines a standard way for AI agents to communicate their intent to generate a user interface. Think of it as a universal UI language that agents can speak to any client application.</p><p>Rather than describing insights, AI describes interfaces. The application then renders those interfaces using its own UI framework, styling and interaction model.</p><p>In this model:</p><ul><li><strong>AI facilitates the UI composition layer.</strong> It reasons over context and emits a structured definition&mdash;a declarative JSON format describing which components to render and what data to populate them with.</li><li><strong>The application remains the source of truth for rendering and behavior.</strong> The client maintains a catalog of pre-approved components.</li><li><strong>The user experiences fully interactive interfaces generated from intent.</strong> Not static mockups. Not screenshots. Real components with real data.</li></ul><p>A2UI effectively shifts AI from being a narrator to being a <strong>composer of user experiences</strong>.</p><h2 id="a2ui-in-telerik-ui-for-blazor">A2UI in Telerik UI for Blazor</h2><p>Within Telerik UI for <a href="https://demos.telerik.com/blazor-ui/a2ui/ai-a2ui" target="_blank">Blazor, A2UI</a> introduces an additional layer on top of the existing component ecosystem. Components are no longer only developer-defined. They become composable primitives available for AI-driven interface construction.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/a2ui-progression.png?sfvrsn=986c7d88_2" alt="Backend, transport like AGUI, A2UI instead of text-only response – dynamically generated UI, Progress components, application" /></p><p>In effect, the library becomes a <strong>structured palette for dynamic interface generation</strong>. <strong>Your design system stays intact.</strong> <strong>Your business logic does not move.</strong></p><h2 id="why-a2ui-makes-sense-agents-think-in-text-users-work-in-interfaces">Why A2UI Makes Sense: Agents Think in Text; Users Work in Interfaces</h2><p>AI is rapidly reshaping how software is built and consumed. Over the past year, we have seen an acceleration of copilots, agents and large language models capable of reasoning over data, executing workflows and assisting users in ways that were previously out of reach.</p><p>Yet, one fundamental limitation remains.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/agents-text-users-interfaces.png?sfvrsn=ba8582d0_2" alt="Agents think in text. Users work in interfaces" /></p><p>AI is exceptionally good at producing text. But applications are not built around text. They are built around interfaces.</p><p>People perceive and interact more easily through visuals&mdash;dashboards, forms, grids, charts, filters, and workflows they can interact with directly. They want to see, explore and act. This is why A2UI has so much potential.</p><p>Modern enterprise applications have long assumed that UI is static. Developers define screens. Users adapt to them. AI, when introduced, typically sits alongside the application as a conversational layer rather than becoming part of the interface itself.</p><p>A2UI introduces the possibility to change that. When AI systems generate structured UI definitions that adapt to context and data, rather than just a block of text, we aren&rsquo;t just enabling what the <em>system says</em>, but what the <em>user sees</em>.</p><p>A single request like &ldquo;analyze my sales performance this quarter&rdquo; can translate into multiple valid experiences depending on who is asking and why&mdash;an executive KPI dashboard, a detailed filterable data grid, a chart-centric exploratory view or a hybrid analytical workspace. The underlying data is the same, but the intent is different. And AI is uniquely positioned to interpret it.</p><h2 id="a2ui-in-practice">A2UI in Practice</h2><p>Use cases for A2UI implementation are as myriad as you can imagine.</p><p>Let&rsquo;s go back to the example of booking a flight. Instead of tedious stepping through pages, one by one, filling and selecting values, you can type: <em>&ldquo;I want to fill a form for booking a flight from London to New York.&rdquo;</em></p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/a2ui-flight-form.png?sfvrsn=6dcce9c5_2" alt="In prompt box, I want to fill a form for booking a flight from London to New York. Then fields for origin, destination, departure date and time, passenger name" /></p><p>The AI interprets that intent and <strong>emits</strong> a UI definition&mdash;a JSON description of a data form (pre-filled with data where possible and using the right components), a results grid (columns: airline, price, departure, duration, stops) and a comparison chart with the top options.</p><p>The Blazor renderer receives that definition and displays the interface using real Telerik UI for Blazor components. The Grid, the Form, the Charts&mdash;all themed and all enabling you to interpret and visualize data as naturally as possible.</p><p>Different users expressing the same intent might receive different representations depending on context, preferences or available data. Maybe one person gets a chart-first view and another gets a table. That is dynamic UI generation in practice.</p><h3 id="a-step-further-building-on-an-emerging-ai-ecosystem">A Step Further: Building on an Emerging AI Ecosystem</h3><p>The flight-booking scenario reflects a much broader shift happening across the AI ecosystem. Applications are moving from static interactions toward dynamic, personalized, agent-driven experiences where intent, reasoning and UI composition are tightly connected.</p><p>This is where the A2UI approach becomes part of a larger architecture rather than a standalone concept.</p><p>In the current implementation, the demo builds on complementary layers that are increasingly relevant in modern AI systems:</p><ul><li><strong>AI interprets user intent</strong>, translating natural language into structured goals and constraints.</li><li>The <a href="https://learn.microsoft.com/en-us/agent-framework/overview/"><strong>Microsoft Agent Framework</strong></a> <strong>orchestrates reasoning and execution</strong>, coordinating tools, workflows and multi-step decision-making across systems.</li><li><a href="https://docs.ag-ui.com/introduction"><strong>AG-UI</strong></a> <strong>provides the interaction contract for agent-driven interfaces</strong>, so UI intent and structure are expressed in a consistent, agent- and renderer-agnostic way.</li><li><strong>The A2UI layer for Telerik components for Blazor defines the structure of the user experience</strong>, describing how the outcome of that reasoning should be expressed as a dynamic, interactive UI rather than plain text.</li><li><strong>Telerik UI for Blazor renders the final experience</strong>, materializing that structure using enterprise-grade components such as grids, forms and charts with full theming, interactivity and performance.</li></ul><p>Together, these components enable AI systems to move beyond conversational output and participate directly in application workflows. Instead of producing text responses, agents can reason over data and shape the user experience itself.</p><h2 id="telerik-a2ui-renderer-for-blazor—availability-preview">Telerik A2UI Renderer for Blazor&mdash;Availability: Preview</h2><p>A2UI is still an emerging concept and ecosystem. Standards, protocols, component contracts and implementation patterns are actively evolving as the industry explores what dynamic UI generation should look like in production applications.</p><p>We are approaching A2UI with the same mindset. The feature is still experimental. As the implementation is evolving, feedback is actively shaping its direction.</p><p>During this phase, we are actively observing real-world usage patterns, gathering feedback from early adopters and refining how intent translates into UI structure. Specifically, we are exploring where AI-generated UI delivers real value and how much control users need to retain over generated interfaces.</p><p>The <a href="https://a2ui.org/specification/v0.8-a2ui/">A2UI specification</a> itself is at v0.8 stable, and the ecosystem around it&mdash;transport layers like AG-UI, Agent Frameworks from Microsoft (hint: we are exploring those too) and others and renderers for different frameworks&mdash;is all moving quickly. This is the right moment to explore the pattern, give us feedback and understand how the renderer model fits your architecture. And get to the next level of creating great UI and UX experience.</p><p>The current A2UI Preview for Telerik UI for Blazor focuses on a constrained but high-impact set of building blocks:</p><ul><li><strong>Input components</strong> &ndash; text fields, date pickers, numeric inputs, dropdowns</li><li><strong>Data Grid</strong> &ndash; data exploration, visualization and comparison</li><li><strong>Charts</strong> &ndash; visual representations that adapt to what the data warrants</li></ul><p>These are intentionally chosen because they represent the backbone of most business applications. This Preview is <strong>opinionated by design</strong>. Rather than attempting to support every UI pattern, it focuses on exploring how intent can reliably map to structured, usable interfaces. <a href="https://demos.telerik.com/blazor-ui/a2ui/ai-a2ui" target="_blank">Go straight to the demo</a>, click and prompt around on an easy-to-follow use case and get in touch with feedback.</p><p>If your organization is exploring AI-powered applications in 2026, this is the ideal time to start a pilot. Use our <a href="https://www.telerik.com/account/support-center/contact-us">Support system</a> to tell us more about your use case or drop us a line in our <a href="https://feedback.telerik.com/blazor">Blazor Feedback Portal</a> or in the <a href="https://www.telerik.com/forums/blazor">Telerik Forums</a>.</p><p>The goal isn&rsquo;t to replace your existing application. The goal is to explore how AI can augment it through interfaces that adapt to the user, the context and the task at hand.</p><h2 id="webmcp-vs.-a2ui-what’s-the-difference">WebMCP vs. A2UI: What&rsquo;s the Difference?</h2><p>If you&rsquo;ve been following our recent AI initiatives, you may have also seen our <a href="https://www.telerik.com/blazor-ui/documentation/ai/web-mcp/overview">Preview support for WebMCP</a>, which enables AI agents to interact with applications through standardized tools and capabilities.</p><p>While both WebMCP and A2UI are part of the broader movement toward AI-native applications, they address different parts of the big picture.</p><p>You can think of them as complementary layers: WebMCP focuses on how <strong>AI agents interact with applications</strong>. A2UI focuses on how <strong>applications interact with users</strong> through AI-generated experiences.</p><p>Together, they create a powerful foundation for AI-native applications and are an extension of our commitment to great UI and UX.</p><p>With WebMCP, an AI agent can discover available actions, execute workflows, retrieve data and interact with business functionality exposed by an application.</p><p>With A2UI, that same agent can dynamically generate forms, grids, charts, dashboards and other user experiences that help users understand, validate and act on the results.</p><p>An AI agent can use WebMCP to retrieve information, execute actions and understand business capabilities, while A2UI can transform those results into dynamic user experiences built with Telerik UI for Blazor components.</p><p>To learn more about our WebMCP Preview support, check out our blog post: <a href="https://www.telerik.com/blogs/telerik-and-kendo-meet-webmcp">Telerik and Kendo UI Meet WebMCP</a>.</p><p>The current capabilities represent only the beginning. The beauty of dynamic UI generation is that the number of possible use cases is virtually unlimited. AI maps context and intent, while Telerik UI for Blazor provides the components that bring those experiences to life.</p><hr /><p>Want to shape how A2UI support for Telerik UI for Blazor develops? Share your thoughts on the <a href="https://feedback.telerik.com/blazor">Blazor Feedback Portal</a> or in the <a href="https://www.telerik.com/forums/blazor">Telerik forums</a>. We are reading everything.</p><p>And don&rsquo;t forget to <a href="https://www.telerik.com/campaigns/telerik-and-kendo-ui-2026-q2-release-webinar">join the webinar</a> for a live demo of A2UI.</p>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:fc4f18d8-9f5a-4741-bfdd-7844b4bdd7e0</id>
    <title type="text">Exploring the SLNX Solution File Format</title>
    <summary type="text">SLNX files are here! In this post, we'll talk about what this format is, why it exists, what it gets right and where you might get tripped up.</summary>
    <published>2026-06-11T19:09:21Z</published>
    <updated>2026-06-18T18:00:59Z</updated>
    <author>
      <name>Dave Brock </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/exploring-slnx-solution-file-format"/>
    <content type="text"><![CDATA[<p><span class="featured">SLNX files are here! In this post, we'll talk about what this format is, why it exists, what it gets right and where you might get tripped up.</span></p><p>If you&rsquo;ve worked with .NET for any length of time, you&rsquo;ve made peace with the <code>.sln</code> file. Not because it&rsquo;s good (it isn&rsquo;t) but because it&rsquo;s the format we have. It&rsquo;s verbose, GUID-laden and a reliable source of merge conflicts on Friday afternoons. It&rsquo;s the format we tolerate, not the one we&rsquo;d choose.</p><p>The good news is there&rsquo;s a new format in town. The <code>.slnx</code> format is Microsoft&rsquo;s XML-based replacement for the venerable <code>.sln</code> file, and it has been steadily gaining first-class support across Visual Studio, the .NET CLI, MSBuild and Rider over the last year. As of <a target="_blank" href="https://devblogs.microsoft.com/dotnet/introducing-slnx-support-dotnet-cli/">.NET 9.0.200</a> and <a target="_blank" href="https://devblogs.microsoft.com/visualstudio/new-simpler-solution-file-format/">Visual Studio 17.13+</a>, you can use it for real projects without crossing your fingers. And <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/core/compatibility/sdk/10.0/dotnet-new-sln-slnx-default">as of .NET 10</a>, <code>dotnet new sln</code> defaults to <code>.slnx</code>.</p><p>In this post, let&rsquo;s talk about what <code>.slnx</code> is, why it exists, what it gets right and where you might get tripped up.</p><h2 id="a-quick-sln-retrospective">A Quick SLN Retrospective</h2><p>To appreciate why we&rsquo;re getting a new format, let&rsquo;s remember what we&rsquo;ve been working with. Here&rsquo;s a typical fragment of an <code>.sln</code> file.</p><pre><code>Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.10.34804.81
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyApi", "MyApi\MyApi.csproj", "{F95781B3-A973-4D19-9585-974DA143E6A1}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8FC526EA-218B-4615-8410-4E1850611F38}"
ProjectSection(SolutionItems) = preProject
Directory.Build.props = Directory.Build.props
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F95781B3-A973-4D19-9585-974DA143E6A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F95781B3-A973-4D19-9585-974DA143E6A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F95781B3-A973-4D19-9585-974DA143E6A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F95781B3-A973-4D19-9585-974DA143E6A1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
</code></pre><p>Oof. The SLN format is a custom Microsoft format that has some weird quirks. Every project gets a GUID. Solution folders are themselves projects with their own GUIDs. Configuration combinations even get cross-multiplied so a four-project solution with two configurations and two platforms produces <em>16 lines</em> of nearly identical setup.</p><p>The pain points are well known to us all. If even two developers add a project at the same time, both edit <code>GlobalSection(ProjectConfigurationPlatforms)</code>, and Git throws its hands up.</p><p>GUIDs, bless their hearts, are not for human eyes. It&rsquo;s a nightmare trying to figure out which project owns which configuration block. And outside of Visual Studio, generating or modifying <code>.sln</code> files reliably often requires <a target="_blank" href="https://microsoft.github.io/slngen/">a tool like <code>slngen</code></a> because no one wants to write the parser themselves.</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">Visual Studio 2026, 6 Months Later</h4></div><div class="col-8"><p class="u-fs16 u-mb0">See what&rsquo;s really behind the <a target="_blank" href="https://www.telerik.com/blogs/visual-studio-2026-6-months-later">Visual Studio 2026 updates</a> and improvements, such as performance, GitHub Copilot, Hot Reload and more.
.</p></div></div><hr class="u-mb3" /></aside><p>This is what the <code>.slnx</code> tries to fix.</p><h2 id="looking-inside-the-.slnx">Looking Inside the .slnx</h2><p>Here&rsquo;s the same project, but expressed as <code>.slnx</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>Solution</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Folder</span> <span class="token attr-name">Name</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>/Solution Items/<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>File</span> <span class="token attr-name">Path</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Directory.Build.props<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>Folder</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Project</span> <span class="token attr-name">Path</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>MyApi/MyApi.csproj<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>Solution</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>Notice how we have no GUIDs, no cross-multiplied configuration table and no <code>EndGlobalSection</code> markers. Even a developer who is foreign to the <code>.slnx</code> format can read it and immediately understand it.</p><p>Expanding a little, here&rsquo;s an example from a layered solution.</p><pre class=" language-xml"><code class="prism  language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Solution</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Folder</span> <span class="token attr-name">Name</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>/Solution Items/<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>File</span> <span class="token attr-name">Path</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>.editorconfig<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>File</span> <span class="token attr-name">Path</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Directory.Build.props<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>File</span> <span class="token attr-name">Path</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Directory.Packages.props<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>Folder</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Folder</span> <span class="token attr-name">Name</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>/src/<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>Project</span> <span class="token attr-name">Path</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>src/Application/Application.csproj<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>Project</span> <span class="token attr-name">Path</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>src/Domain/Domain.csproj<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>Project</span> <span class="token attr-name">Path</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>src/Infrastructure/Infrastructure.csproj<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>Project</span> <span class="token attr-name">Path</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>src/Web.Api/Web.Api.csproj<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>Folder</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Folder</span> <span class="token attr-name">Name</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>/tests/<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>Project</span> <span class="token attr-name">Path</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>tests/UnitTests/UnitTests.csproj<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>Project</span> <span class="token attr-name">Path</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>tests/IntegrationTests/IntegrationTests.csproj<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>Folder</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>Solution</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>If you compare this to the equivalent <code>.sln</code>, you&rsquo;re looking at maybe a third of the lines, with none of the GUID overhead. If you need to override configurations for a specific project (like, say your test project shouldn&rsquo;t build in Release mode) you can express that with a <code>Configurations</code> element on the project. In reality, most projects won&rsquo;t need it at all. If you do, you can review examples in <a target="_blank" href="https://github.com/microsoft/vs-solutionpersistence"><code>microsoft/vs-solutionpersistence</code></a> and its <a target="_blank" href="https://github.com/microsoft/vs-solutionpersistence/wiki/Samples">samples wiki</a>.</p><p>If the <code>.slnx</code> looks similar to a <code>.csproj</code>, that&rsquo;s intentional. Microsoft&rsquo;s reasoning is that XML is already the format the rest of MSBuild speaks, the team already had a parser for it, and features like comments and attributes are first-class citizens. A JSON or YAML format might have been trendier, but it would have forced the team to invent semantics for things <code>.csproj</code> already has solved problems for.</p><h2 id="migrating-existing-projects">Migrating Existing Projects</h2><p>Existing projects are a different conversation. Here&rsquo;s what you get by migrating, and how to do it cleanly.</p><ul><li><strong>Merge conflicts are more manageable.</strong> Because there are no GUIDs that change every time you touch a project, the diffs you see in PRs are meaningful. If you add a project, you see one line added. Move a project to a different folder, and you see the path change. Nobody has to play GUID detective.</li><li><strong>You can reasonably edit by hand.</strong> You&rsquo;ll never want to hand-edit a <code>.sln</code> unless it&rsquo;s an emergency. With <code>.slnx</code>, opening it in your editor of choice is perfectly reasonable. I&rsquo;ve fixed broken solution layouts in a text editor faster than Visual Studio would have let me click through the dialogs.</li><li><strong>Tooling has standardized what a solution is.</strong> Microsoft has open-sourced the parser as the <a target="_blank" href="https://www.nuget.org/packages/Microsoft.VisualStudio.SolutionPersistence/">Microsoft.VisualStudio.SolutionPersistence NuGet package</a>. This means MSBuild, the .NET CLI and third-party tools are all reading the same file the same way.</li></ul><p>Based on the official <code>dotnet sln</code> reference, the <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-sln">actual conversion is one command</a>.</p><pre><code>dotnet sln migrate
</code></pre><p>If you run this in a directory with an <code>.sln</code> file, you&rsquo;ll get an <code>.slnx</code> file beside it. From Visual Studio, you can also use <strong>File -&gt; Save Solution As&hellip;</strong> and pick <strong>XML Solution File (.slnx)</strong> from the dropdown. Rider supports a similar flow.</p><p>You don&rsquo;t want to keep both files in the repository. The <code>dotnet sln</code> commands don&rsquo;t reliably pick the right one when you have both, and it&rsquo;s easy for the two to get out of sync as people add or remove projects. Pick when to cut over, delete the old file and move on. While there is <a target="_blank" href="https://github.com/edvilme/dotnet-sln-sync">a community tool</a> called <code>dotnet-sln-sync</code> that helps, that should only be used temporarily and not long term.</p><p>As you convert over, here&rsquo;s a quick checklist to avoid any headaches:</p><ul><li><strong>Verify your <code>global.json</code> allows the right SDK.</strong> You need at least .NET 9.0.200 for full CLI support. If your <code>global.json</code> pins to something older, the migrate command won&rsquo;t work.</li><li><strong>Check your CI build agents.</strong> If you&rsquo;re on self-hosted Azure DevOps agents, for example, confirm they&rsquo;re running an SDK new enough to handle <code>.slnx</code>.</li><li><strong>Audit your pipelines.</strong> Anywhere you have references directly to <code>.sln</code>, update the path. Wildcard references like <code>**/*.sln</code> will miss the new file. If you are using templates where you have a mix of <code>.sln</code> and <code>.slnx</code>, a pattern like <code>**/*.sln*</code> will be useful.</li><li><strong>Look out for dependent tools.</strong> Tools like <code>slngen</code> don&rsquo;t yet support <code>.slnx</code> at the time of this post (you can <a target="_blank" href="https://github.com/microsoft/slngen/issues/643">track the issue here</a>). If you&rsquo;re reliant on a tool outside of the Microsoft umbrella, check before you migrate.</li></ul><h2 id="some-rough-edges">Some Rough Edges</h2><p>The <code>.slnx</code> format is no longer &ldquo;preview&rdquo; in any practical sense, but the ecosystem around it is still catching up.</p><ul><li><strong>Visual Studio file association.</strong> Double-clicking a <code>.slnx</code> file doesn&rsquo;t open Visual Studio by default. You can fix this with a file association or just open it from inside the IDE.</li><li><strong>C# Dev Kit in VS Code.</strong> It works, but you may need to set <a target="_blank" href="https://devblogs.microsoft.com/dotnet/introducing-slnx-support-dotnet-cli/"><code>dotnet.defaultSolution</code></a> to the path of your <code>.slnx</code> if it isn&rsquo;t auto-detected.</li><li><strong>Globbing.</strong> You can&rsquo;t write <code>&lt;Project Path="src/**/*.csproj" /&gt;</code> and have it discover projects automatically. The team&rsquo;s reasoning, captured in the <a target="_blank" href="https://github.com/microsoft/vs-solutionpersistence/issues/61">globbing feature request</a>, is that globbing slows down solution loading on large repos because Visual Studio has to scan the file system before it can render anything. That makes sense, <em>but</em> I think plenty of us would happily trade a slower cold load for never having to add another project entry by hand.</li><li><strong>Third-party tools.</strong> Anything that parses <code>.sln</code> directly needs to update. Hopefully the open-source parser library accelerates that, but you&rsquo;ll find holdouts.</li></ul><p>None of these are dealbreakers for new projects. For older codebases with a lot of tooling baked in, it&rsquo;s worth doing a small pilot before flipping the whole repo.</p><h2 id="wrapping-up">Wrapping Up</h2><p>The <code>.slnx</code> format isn&rsquo;t a revolutionary feature. It doesn&rsquo;t change how you write code, it doesn&rsquo;t add new build capabilities, and it doesn&rsquo;t speed up your application. It is, however, one of those quality-of-life upgrades that quietly results in fewer merge conflicts, less time staring at GUIDs, less friction in your CI pipelines once they&rsquo;re set up.</p><p>If you&rsquo;re starting a new .NET solution, just use <code>.slnx</code> from day one. And with .NET 10, you&rsquo;ll get it without lifting a finger. If you&rsquo;re maintaining an existing <code>.sln</code> file, plan a migration when you have a slow week, audit your tooling and cut over cleanly. The format isn&rsquo;t going anywhere, and getting ahead of the transition is easier than waiting for some downstream tool to force your hand.</p><p>Thanks for reading, and happy coding!</p><h2 id="further-reading">Further Reading</h2><ul><li><a target="_blank" href="https://devblogs.microsoft.com/visualstudio/new-simpler-solution-file-format/">New, simpler solution file format</a></li><li><a target="_blank" href="https://devblogs.microsoft.com/dotnet/introducing-slnx-support-dotnet-cli/">Introducing support for SLNX in the .NET CLI</a></li><li><a target="_blank" href="https://github.com/microsoft/vs-solutionpersistence"><code>microsoft/vs-solutionpersistence</code></a></li><li><a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-sln"><code>dotnet sln</code> command reference</a></li><li><a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/core/compatibility/sdk/10.0/dotnet-new-sln-slnx-default">.NET 10 breaking change: <code>dotnet new sln</code> defaults to SLNX</a></li></ul>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:7ce6c14b-f242-4a6e-8889-98c451e38cbd</id>
    <title type="text">Why You’ve Outgrown Free &amp; OpenSource React UI Libraries</title>
    <summary type="text">Free UI libraries work great… until they don’t. Many teams outgrow them without realizing it—until things start slowing down.</summary>
    <published>2026-06-11T16:44:30Z</published>
    <updated>2026-06-18T18:00:59Z</updated>
    <author>
      <name>Hassan Djirdeh </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/why-youve-outgrown-free-open-source-react-ui-libraries"/>
    <content type="text"><![CDATA[<span class="featured"><p>TL;DR:</p><ul><li>Free libraries are great early</li><li>They break at scale</li><li>The cost becomes engineering time, not just licensing</li><li>That&rsquo;s when teams start evaluating alternatives</li></ul></span>
<p>The challenge isn&rsquo;t the license cost.</p><p>The real cost of free UI libraries shows up later&mdash;in engineering time, maintenance overhead, and the effort required to make everything work together at scale.</p><p><strong>Free UI libraries work great&hellip; until they don&rsquo;t.</strong></p><p>And when they start breaking, it&rsquo;s rarely obvious at first. Many teams outgrow them without realizing it&mdash;until things start slowing down.</p><p>Free and open-source UI libraries are a natural starting point for many React projects. They&rsquo;re familiar, community-driven and come with zero upfront cost. For prototypes, MVPs and simpler applications, they often work exactly as expected.</p><p>However, as products grow, their limitations tend to surface gradually. A data grid that handled a few hundred rows starts to slow down at scale. A dependency becomes unmaintained. Different UI pieces begin to behave inconsistently. Similar cracks can appear across other foundational components.</p><p>In this article, we&rsquo;ll explore the signs that your project may have outgrown free UI libraries and how to evaluate whether it&rsquo;s time to consider a more comprehensive solution.</p><h2 id="when-free-ui-libraries-make-sense">When Free UI Libraries Make Sense</h2><p>It&rsquo;s important to note that free UI libraries can be genuinely useful. Libraries like <a target="_blank" href="https://react-bootstrap.github.io/">React Bootstrap</a>, <a target="_blank" href="https://www.radix-ui.com/">Radix</a> and <a target="_blank" href="https://headlessui.com/">Headless UI</a> have made it easier for developers to build interfaces quickly. They lower the barrier to entry, provide solid defaults and benefit from large communities of contributors who improve them over time.</p><p>For prototyping, MVPs and projects where the component requirements are straightforward, free libraries are often the most practical choice. There&rsquo;s no procurement process, no license to manage and the ecosystem familiarity means new team members can ramp up quickly.</p><p>The challenge arises when the project scales, whether in terms of data volume, feature complexity or team size. The limitations that didn&rsquo;t matter at 500 users start to surface at 50,000, and at that point we may need to take additional steps to extend or replace UI components rather than expecting them to scale with us.</p><h2 id="performance-at-scale">Performance at Scale</h2><p>One of the first places teams feel the strain is <strong>performance</strong>. A table component that renders a few hundred rows without issue can become a bottleneck when the data grows into the thousands or tens of thousands. This is a common scenario in enterprise applications where dashboards, admin panels and reporting tools need to handle very large datasets.</p><p>To go into a bit more detail, many free table and grid components render all rows into the DOM at once. When the dataset is small, this is fine, but with very large datasets, users can experience noticeable lag and slow scroll performance.</p><pre class=" language-jsx"><code class="prism  language-jsx"><span class="token comment">// A basic table rendering all rows directly</span>
<span class="token keyword">const</span> <span class="token function-variable function">BasicTable</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">{</span> data <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>table</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>thead</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>tr</span><span class="token punctuation">&gt;</span></span>
          <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>th</span><span class="token punctuation">&gt;</span></span>Name<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>th</span><span class="token punctuation">&gt;</span></span>
          <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>th</span><span class="token punctuation">&gt;</span></span>Email<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>th</span><span class="token punctuation">&gt;</span></span>
          <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>th</span><span class="token punctuation">&gt;</span></span>Status<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>th</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>tr</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>thead</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>tbody</span><span class="token punctuation">&gt;</span></span>
        <span class="token punctuation">{</span>data<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span>row<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span>
          <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>tr</span> <span class="token attr-name">key</span><span class="token script language-javascript"><span class="token punctuation">=</span><span class="token punctuation">{</span>row<span class="token punctuation">.</span>id<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>td</span><span class="token punctuation">&gt;</span></span><span class="token punctuation">{</span>row<span class="token punctuation">.</span>name<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>td</span><span class="token punctuation">&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>td</span><span class="token punctuation">&gt;</span></span><span class="token punctuation">{</span>row<span class="token punctuation">.</span>email<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>td</span><span class="token punctuation">&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>td</span><span class="token punctuation">&gt;</span></span><span class="token punctuation">{</span>row<span class="token punctuation">.</span>status<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>td</span><span class="token punctuation">&gt;</span></span>
          <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>tr</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 tag"><span class="token tag"><span class="token punctuation">&lt;/</span>tbody</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>table</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>
</code></pre><p>In the above basic example, once the dataset reaches 5,000 or 10,000 entries, the browser is rendering thousands of DOM nodes simultaneously. This causes scrolling to slow down, filtering to become sluggish and the overall user experience to degrade.</p><aside><hr /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">The Great React Grid-Off: KendoReact vs. MUI vs. AG Grid</h4></div><div class="col-8"><p class="u-fs16 u-mb0">Curious how the best React grids on the market compete head-to-head? <a target="_blank" href="https://www.telerik.com/blogs/great-react-grid-off-kendoreact-vs-mui-vs-ag-grid">KendoReact vs. MUI vs. AG Grid</a>&mdash;let&rsquo;s see it!</p></div></div><hr class="u-mb3" /></aside><p>To address this, teams typically need to implement features like row virtualization, which only renders the rows currently visible in the viewport. Building this from scratch or layering it on top of a free component that wasn&rsquo;t designed for it is where the engineering cost starts to compound. We might leverage a third-party library like <a target="_blank" href="https://github.com/bvaughn/react-virtualized">react-virtualization</a>, or we might end up managing a container ref, a <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver">ResizeObserver</a>, absolute positioning and scroll offset calculations ourselves.</p><pre class=" language-jsx"><code class="prism  language-jsx"><span class="token comment">// pseudo-code</span>
<span class="token keyword">const</span> parentRef <span class="token operator">=</span> <span class="token function">useRef</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> virtualizer <span class="token operator">=</span> <span class="token function">useVirtualizer</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  count<span class="token punctuation">:</span> data<span class="token punctuation">.</span>length<span class="token punctuation">,</span>
  getScrollElement<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> parentRef<span class="token punctuation">.</span>current<span class="token punctuation">,</span>
  estimateSize<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token number">36</span><span class="token punctuation">,</span>
  overscan<span class="token punctuation">:</span> <span class="token number">5</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 punctuation">(</span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">ref</span><span class="token script language-javascript"><span class="token punctuation">=</span><span class="token punctuation">{</span>parentRef<span class="token punctuation">}</span></span> <span class="token attr-name">style</span><span class="token script language-javascript"><span class="token punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span> height<span class="token punctuation">:</span> <span class="token string">"500px"</span><span class="token punctuation">,</span> overflow<span class="token punctuation">:</span> <span class="token string">"auto"</span> <span class="token punctuation">}</span><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">style</span><span class="token script language-javascript"><span class="token punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span> height<span class="token punctuation">:</span> virtualizer<span class="token punctuation">.</span><span class="token function">getTotalSize</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> position<span class="token punctuation">:</span> <span class="token string">"relative"</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></span><span class="token punctuation">&gt;</span></span>
      <span class="token punctuation">{</span>virtualizer<span class="token punctuation">.</span><span class="token function">getVirtualItems</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span>virtualRow<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span>
        <span class="token operator">&lt;</span>div
          key<span class="token operator">=</span><span class="token punctuation">{</span>virtualRow<span class="token punctuation">.</span>key<span class="token punctuation">}</span>
          style<span class="token operator">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span>
            position<span class="token punctuation">:</span> <span class="token string">"absolute"</span><span class="token punctuation">,</span>
            top<span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">,</span>
            transform<span class="token punctuation">:</span> <span class="token template-string"><span class="token string">`translateY(</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>virtualRow<span class="token punctuation">.</span>start<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">px)`</span></span><span class="token punctuation">,</span>
            height<span class="token punctuation">:</span> <span class="token template-string"><span class="token string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>virtualRow<span class="token punctuation">.</span>size<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">px`</span></span><span class="token punctuation">,</span>
          <span class="token punctuation">}</span><span class="token punctuation">}</span>
        <span class="token operator">&gt;</span>
          <span class="token punctuation">{</span>data<span class="token punctuation">[</span>virtualRow<span class="token punctuation">.</span>index<span class="token punctuation">]</span><span class="token punctuation">.</span>name<span class="token punctuation">}</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 punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</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 punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>This is just for a concept like row virtualization. We still need to layer on sorting, filtering, grouping, column pinning and keyboard navigation. Each of those features introduces its own state management, edge cases and regression risk. What started as a simple table becomes an internal platform project. At this point, the problem is no longer just performance. It&rsquo;s the engineering time required to implement, test, and maintain these solutions on top of tools that weren&rsquo;t designed for them.</p><h3 id="what-this-looks-like-with-kendoreact">What This Looks Like with KendoReact</h3><p>The <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/grid/">React Data Grid</a> in the Progress KendoReact component library is built for exactly this scenario. Virtual scrolling (i.e., row virtualization) is enabled by default, meaning the Grid only renders visible rows plus a small buffer. It handles millions of records with constant memory usage, maintains 60 fps scroll performance and keeps the DOM lightweight regardless of dataset size.</p><p><a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/grid/optimization/column-virtualization">Column virtualization</a> is also available for datasets with many columns, rendering only the columns visible during horizontal scrolling.</p><p>Instead of wiring all of this ourselves, our Grid setup can look as simple as this:</p><pre class=" language-jsx"><code class="prism  language-jsx"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Grid</span>
  <span class="token attr-name">data</span><span class="token script language-javascript"><span class="token punctuation">=</span><span class="token punctuation">{</span>data<span class="token punctuation">}</span></span>
  <span class="token attr-name">autoProcessData</span><span class="token script language-javascript"><span class="token punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span>
    filter<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
    sort<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
    group<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
    page<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">}</span></span>
  <span class="token attr-name">filterable</span>
  <span class="token attr-name">sortable</span>
  <span class="token attr-name">groupable</span>
  <span class="token attr-name">pageable</span>
<span class="token punctuation">/&gt;</span></span>
</code></pre><p>Sorting, filtering, grouping and paging are all handled through the autoProcessData prop. No manual state wiring and no glue code between separate virtualization, sorting and filtering libraries. With regards to performance, here&rsquo;s a visual example of how the grid behaves when loading and scrolling within a million+ records:</p><p>You can test the performance of the KendoReact Data Grid in the <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/grid/performance">Performance | KendoReact Data Grid documentation</a>.</p><p>Beyond tables, similar performance ceilings can show up in complex components like <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/scheduler">schedulers</a>, <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/treeview">tree views</a> and <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/charts">charts</a> when the volume of data or the complexity of interactions increases.</p><h2 id="maintenance-risk-and-the-support-gap">Maintenance Risk and the Support Gap</h2><p>Fully open-source projects are maintained by people, and people have lives outside of code. Maintainers change jobs, experience burnout, shift their focus to new projects or simply move on. This is completely understandable and is not a criticism of the open-source community, but it is a risk that teams need to account for.</p><p>With the emergence of AI, open-source maintainers now face an additional burden: a growing flood of AI-generated pull requests and bug reports that consume their limited time and energy, <a target="_blank" href="https://github.com/matplotlib/matplotlib/pull/31132#issuecomment-3882240722">as this real GitHub comment from an AI agent illustrates</a>.</p><p>We&rsquo;ve all opened a GitHub repository that looks healthy at first glance, with thousands of stars and active discussions. Then we notice the last meaningful commit was over a year ago. If the library sits at the core of our UI, the options aren&rsquo;t ideal: we can wait and hope it gets attention again, or we can fork it and take on maintenance ourselves.</p><p>This risk compounds when something goes wrong in production. With free libraries, the support path typically looks like this: search GitHub Issues (or create our own), post a question on Stack Overflow or dig through the source code ourselves. These are all viable approaches, but none of them come with a guaranteed response time. Over time, this becomes ongoing maintenance overhead&mdash;time spent tracking issues, adapting to breaking changes or maintaining internal forks instead of building product features.</p><p>Commercial UI libraries like KendoReact <strong>change both sides of this equation</strong>. They have dedicated engineering teams whose job is to maintain, update and improve the components. <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/changelogs/ui-for-react">Release schedules</a> are predictable, breaking changes are documented and there&rsquo;s an organizational commitment to long-term support. When we open a support ticket, a team that knows the codebase intimately is responsible for helping us resolve the issue. This doesn&rsquo;t eliminate debugging entirely, but it significantly reduces the time spent on problems that originate outside our own code.</p><h3 id="the-integration-tax">The Integration Tax</h3><p>A less obvious cost of relying on free libraries is what we might call the integration tax. No single free library covers every component a modern application needs, so teams often end up pulling from multiple sources. A date picker from one library, a data grid from another, form components from a third, a modal dialog from yet another.</p><p>Each of these libraries has its own API patterns, styling approach and theming mechanism. Keeping them all compatible requires effort that isn&rsquo;t always visible in sprint planning.</p><pre class=" language-jsx"><code class="prism  language-jsx"><span class="token comment">// The import sprawl of mixing multiple UI libraries</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> DatePicker <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"react-datepicker-lib"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> DataGrid <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"another-grid-lib"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> Modal <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"yet-another-modal-lib"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> Select <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"some-select-lib"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> Tabs <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"a-tabs-package"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> Tooltip <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"tooltip-library-xyz"</span><span class="token punctuation">;</span>

<span class="token comment">// Each with its own:</span>
<span class="token comment">// - Styling system (CSS modules, styled-components, plain CSS, Tailwind)</span>
<span class="token comment">// - Event handling patterns</span>
<span class="token comment">// - TypeScript type definitions (or lack thereof)</span>
<span class="token comment">// - Breaking change policies</span>
</code></pre><p>This illustrates the integration tax: the hidden engineering effort required to make multiple tools behave like a cohesive system, and to keep them that way over time.</p><p>The above is a very simplified illustration, but the pattern can be understood. When different UI packages and tooling each have their own way of handling themes, events and styling, the glue code needed to make them work together becomes maintenance of its own.</p><p>A <a target="_blank" href="https://www.telerik.com/kendo-react-ui">comprehensive enterprise-ready React UI library</a>, by comparison, provides a single API surface, a unified theming system and components that are designed to work together from the start. The date picker, the grid, the forms, the dialogs, the scheduler are all built by the same team, tested together and styled consistently.</p><h2 id="accessibility-and-compliance-at-scale">Accessibility and Compliance at Scale</h2><p>Accessibility is increasingly moving from a &ldquo;nice to have&rdquo; to a legal requirement. The <a target="_blank" href="https://www.telerik.com/blogs/what-does-european-accessibility-act-mean-developers">European Accessibility Act</a>, which went into effect on June 28, 2025, makes accessibility legally required for digital products used in the EU. In the United States, <a target="_blank" href="https://www.telerik.com/blogs/hidden-costs-inaccessible-apps-enterprises-how-avoid">accessibility lawsuits continue to rise</a>, targeting businesses of all sizes.</p><p>Many free UI libraries include some level of accessibility support, but the depth and consistency of that support vary widely. ARIA attributes might be present on some components but missing on others. Keyboard navigation might work for basic interactions, but break down in more complex flows and screen reader support might be untested or incomplete.</p><p>For teams that need to meet <a target="_blank" href="https://www.w3.org/TR/WCAG22/">WCAG 2.2</a> or <a target="_blank" href="https://www.w3.org/TR/WCAG21/">WCAG 2.1</a> compliance, this means auditing every component, identifying gaps and writing custom code to fill them. That audit and remediation process can be substantial, especially when dealing with complex interactive components like grids, trees and menus, where the accessibility requirements are non-trivial.</p><p>Commercial UI libraries that treat accessibility as a first-class concern build it into the component architecture from the beginning. This includes proper ARIA roles and attributes, keyboard navigation, focus management, high-contrast theme support and screen reader compatibility across components.</p><p>The cost of getting this right is absorbed by the library team rather than being passed on to every application team that uses the components. For KendoReact, as an example, <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/accessibility">accessibility compliance is a strategic and ongoing commitment</a>.</p><h2 id="when-do-we-outgrow-free-ui-libraries">When Do We Outgrow Free UI Libraries?</h2><p>Free UI libraries serve an important role in the React ecosystem. They lower barriers, accelerate early development and provide solid building blocks for countless projects. Choosing them at the start of many projects can be the right decision.</p><p>KendoReact also includes a <a target="_blank" href="https://www.telerik.com/kendo-react-ui/free-react-components">Free tier</a> with 50+ production-ready React components available for use without a license.</p><p>However, projects evolve, and tools that worked well at one stage can become sources of friction at another. You may have outgrown free UI libraries if:</p><ul><li>You&rsquo;re spending time implementing features like virtualization or complex state handling yourself.</li><li>You&rsquo;re maintaining forks of third-party components.</li><li>You&rsquo;re integrating multiple libraries with different APIs and styling systems.</li><li>Accessibility or compliance requires additional engineering effort.</li><li>Fixing UI issues depends on external maintainers or community responses.</li></ul><p>The question isn&rsquo;t whether free libraries are good or bad. It&rsquo;s whether the total cost of using them, including developer time for workarounds, is still better than the alternative. Teams shifts from asking &ldquo;Why pay for UI components?&rdquo; to &ldquo;What is the cost of continuing to build and maintain this ourselves?&rdquo;</p><p>For teams that have reached that stage, it&rsquo;s worth stepping back and evaluating the options more deliberately, including what a more integrated, enterprise-ready approach can offer in terms of performance, consistency and long-term reliability. This is where robust professional libraries like <a target="_blank" href="https://www.telerik.com/kendo-react-ui">KendoReact</a> step in, offering production-ready components, built-in performance optimizations, and a unified system that reduces the need for custom integrations and ongoing maintenance work.</p><p>For additional details on KendoReact and what it offers, check out the following resources:</p><ul><li><a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/">KendoReact Examples &amp; Demos</a></li><li><a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/accessibility">KendoReact Accessibility</a></li><li><a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/grid/performance">KendoReact Data Grid Performance</a></li><li><a target="_blank" href="https://www.telerik.com/themebuilder">KendoReact ThemeBuilder</a></li></ul>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:9c42fac5-42a2-417f-b5a6-e359bcaadc44</id>
    <title type="text">SCIM Provisioning for DevCraft Subscriptions: Automating License Management for Your Teams</title>
    <summary type="text">Progress DevCraft Complete and DevCraft Ultimate subscription licenses just got a whole lot easier to manage across your team with SCIM provisioning.</summary>
    <published>2026-06-10T18:13:57Z</published>
    <updated>2026-06-18T18:00:59Z</updated>
    <author>
      <name>Dragan Grigorov </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/scim-provisioning-telerik-kendo-ui-licenses"/>
    <content type="text"><![CDATA[<p><span class="featured">Progress DevCraft Complete and DevCraft Ultimate subscription licenses just got a whole lot easier to manage across your team with SCIM provisioning.</span></p><p>Managing access across growing development teams can quickly become time-consuming when done manually.</p><p>That&rsquo;s why we&rsquo;re introducing <strong>System for Cross-domain Identity Management (SCIM)</strong> for Progress DevCraft Complete and DevCraft Ultimate subscription licenses, along with Fiddler and ThemeBuilder Enterprise licenses&mdash;bringing automated license seat management directly into your organization&rsquo;s Identity Provider.</p><p>If <strong>SSO simplified how your team signs in</strong>, SCIM extends that experience by <strong>automating how access is assigned and maintained</strong>.</p><p> If you haven&rsquo;t set up SSO yet, follow the steps in our <a target="_blank" href="https://www.telerik.com/blogs/sso-telerik-kendo-ui-simpler-more-secure-access-account">SSO guide</a>.</p><h2 id="what-is-scim-and-why-it-matters">What Is SCIM and Why It Matters</h2><p>For customers using <strong>DevCraft Complete and Ultimate subscription licenses</strong>, as well as <strong>Fiddler</strong> and <strong>ThemeBuilder Enterprise licenses</strong>, SCIM allows license access to be managed directly through your organization&rsquo;s Identity Provider (IdP), such as Microsoft Entra ID or Okta.</p><p>Instead of managing users separately in Telerik, access becomes part of your existing internal processes. When developers are assigned to the right group in your IdP, they automatically receive access to their DevCraft subscription. When they are removed, their access is updated accordingly.</p><p>This helps organizations:</p><ul><li>Keep license access aligned with internal systems</li><li>Eliminate manual seat management</li><li>Reduce the risk of unused or outdated assignments</li></ul><p> Your Identity Provider becomes the <strong>single place to manage access</strong> to your licenses.</p><h2 id="how-to-set-it-up">How to Set It Up</h2><p>SCIM builds on top of SSO and is designed to be quick to configure.</p><h3 id="enable-sso">1. Enable SSO</h3><p>SCIM requires SSO to be already configured for your domain.</p><p>If you haven&rsquo;t enabled it yet, follow the steps in our <a target="_blank" href="https://www.telerik.com/blogs/sso-telerik-kendo-ui-simpler-more-secure-access-account">SSO guide</a>.</p><h3 id="open-scim-setup-in-your-telerik-account">2. Open SCIM Setup in Your Telerik Account</h3><ul><li>Go to <a target="_blank" href="https://www.telerik.com/account/sso-management"><strong>Manage SSO and SCIM</strong></a> in your Telerik account.</li><li>Click on <strong>SCIM Setup</strong>.</li></ul><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/scim-setup.png?sfvrsn=5f9e7cc_2" alt="Configure SSO & SCIM: SCIM Setup" /></p><h3 id="generate-your-scim-key">3. Generate Your SCIM Key</h3><p>In the SCIM Setup screen:</p><ul><li>Copy the SCIM URL and use it when configuring your Identity Provider.</li><li>Add a Key Note that should help differentiate your keys in the future.</li><li>Generate and copy the <strong>SCIM API key</strong> and <strong>Finish Setup</strong></li></ul><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/generate-scim-key.png?sfvrsn=8870d1e8_2" alt="SCIM URL, Key Note, Key" /></p><blockquote><p>⚠️ <strong>Important:</strong> Please copy the key, as once the setup is complete, you will no longer have access to the full key.</p></blockquote><p>The generated SCIM API Key will be used as a Bearer authentication token in your IdP.</p><h3 id="configure-scim-in-your-identity-provider">4. Configure SCIM in Your Identity Provider</h3><p>This step connects your Identity Provider with Telerik, allowing it to send user and group updates automatically.</p><p>In your Identity Provider (for example, Okta):</p><ul><li>Add the Telerik SCIM URL.</li><li>Use the generated API key for authentication.</li></ul><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/scim-provision-integration.png?sfvrsn=7651526f_2" alt="Okta window for Telerik SCIM Test App. Provisioning, Integration tab. SCIM 2.0 Base Url, OAuth Bearer Token" /></p><ul><li>Enable provisioning&mdash;verify the Create, Update and Deactivate are enabled.</li></ul><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/okta-scim-provision.png?sfvrsn=59e4ccad_2" alt="Okta window for Telerik SCIM Test App. Provisioning, To App tab. Okta to SCIM. Create users, update user attributes, deactivate users are enabled" /></p><h3 id="map-your-devcraft-license-to-your-idp-group">5. Map Your DevCraft License to Your IdP Group</h3><p>In <a target="_blank" href="https://www.telerik.com/account/sso-management">Manage SSO &amp; SCIM</a> in Your Account:</p><ul><li>Identify your DevCraft Complete or Ultimate subscription license that you want to enable the SCIM feature, and click on <strong>Configure</strong>.</li><li><strong>Enable SCIM Provisioning</strong>.</li><li>Copy the <strong>IdP Group Identifier</strong> and <strong>Apply</strong>.</li></ul><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/configure-sso-scim-provisioning.png?sfvrsn=73394aee_2" alt="Configure SSO & SCIM Provisioning for License Name" /></p><p> From this point on, access is controlled through group membership.</p><ul><li>Use the IdP Group Identifier to <strong>create a group with the same name in your IdP Provider</strong> and assign that group to the SCIM App. Optionally, add a group description as a human-readable name in the IdP.</li></ul><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/okta-group-idp-provider.png?sfvrsn=4af0484b_2" alt="Okta window for Telerik SCIM Test App. Assignments, Groups. Subscription key." /></p><ul><li>You can start adding users to the IdP group. For a start, if there are already seated users on the Telerik side, add those users to the IdP group as well. From there on, any IdP group change will be reflected on the Telerik side.</li></ul><h3 id="verify-user-synchronization">6. Verify User Synchronization</h3><p>After completing the setup, you can verify that the configuration is correct and that users are syncing as expected from the <a target="_blank" href="https://www.telerik.com/account/manage-licensed-users/product-list">Licensed Users</a> view.</p><ul><li>Users added to the mapped IdP group will start appearing automatically.</li><li>Existing users (with Telerik accounts) should show as <strong>Assigned</strong>, and can continue signing in via SSO as before.</li><li>New users (without a Telerik account) will appear with an <strong>Invited</strong> status. They will receive an email invitation. They need to complete their account setup on their first login.</li></ul><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/devcraft-complete-subscription-developer-list.png?sfvrsn=b22a1a8e_2" alt="Licensed Users page. DevCraft Complete Subscriptions. List of developers with states invited or assigned." /></p><p> This handles both new and existing users seamlessly.</p><h2 id="what-happens-after-scim-is-enabled">What Happens After SCIM Is Enabled?</h2><p>Once SCIM is enabled for your license, access management becomes fully automated.</p><p>User access is driven entirely by group membership in your Identity Provider, while Telerik reflects those changes automatically. New users are guided through a simple onboarding experience at login, and the Licensed Users view becomes read-only for visibility and reporting.</p><p> There&rsquo;s no need to manually manage seats in Telerik.-Your Identity Provider handles all updates.</p><h2 id="bring-automated-access-to-your-telerik-licenses">Bring Automated Access to Your Telerik Licenses</h2><p>SCIM makes it easier to manage access to your licenses by extending your existing identity management into license management.</p><p>Once combined with SSO, it provides a smooth experience where both authentication and access are handled together&mdash;directly from your Identity Provider.</p><p>Whether you&rsquo;re looking to reduce manual work, keep access aligned with your organization or scale more efficiently as your team grows, SCIM is designed to help you get there!</p><p>Head to the <a target="_blank" href="https://www.telerik.com/account/sso-management">Manage SSO and SCIM</a> page now to get started.</p>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:e5d8edcc-5c10-4953-9ebb-fda28d4b3b83</id>
    <title type="text">Is it Safe? Creating Reliable AI Agents with Progress Agentic RAG</title>
    <summary type="text">The Progress Agentic RAG no-code, software-as-a-service solution lets you create an AI agent in minutes. But, after that, you need to validate whether your agent is providing relevant, accurate and complete responses.</summary>
    <published>2026-06-09T20:43:28Z</published>
    <updated>2026-06-18T18:00:59Z</updated>
    <author>
      <name>Peter Vogel </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/is-it-safe-creating-reliable-ai-agents-progress-agentic-rag"/>
    <content type="text"><![CDATA[<p><span class="featured">The Progress Agentic RAG no-code, software-as-a-service solution lets you create an AI agent in minutes. But, after that, you need to validate whether your agent is providing relevant, accurate and complete responses.</span></p><p>You know that AI agents using Large Language Models (LLMs) are prone to &ldquo;hallucinations&rdquo; by providing responses that aren&rsquo;t grounded in reality (insert your favorite story here). There is a solution for hallucinating agents: <a target="_blank" href="https://www.telerik.com/blogs/understanding-rag-retrieval-augmented-generation">Retrieval-Augmented Generation (RAG) knowledge sources</a>, used along with your LLM. A RAG-enabled agent contains resources (i.e., documents and websites) containing information related to your organization and the agent&rsquo;s topic area that help your AI agent provide grounded responses.</p><p>You can create a RAG-enabled agent by writing code (I just finished a series on how to <a target="_blank" href="https://www.telerik.com/blogs/creating-custom-ai-agent-telerik-tools-1-configuring-llm-azure-ollama">create agents with code using Progress Telerik tools</a>)&mdash;a great approach when you need an agent tightly coupled to a specific application or an agent that needs a ton of flexibility at runtime to dynamically select its resources. Like any software project, a code-based approach can be expensive and time consuming.</p><p><a target="_blank" href="https://www.progress.com/agentic-rag">Progress Agentic RAG</a>, on the other hand, will let you, it in a few minutes and without writing a line of code, create a RAG-enabled agent that can be used by your whole organization.</p><p>Creating an agent is a two-step process: First, of course, you need to create your agent. But, before you start using your agent (or make your agent available for other people to use it), you need to validate your agent&rsquo;s responses.</p><h2 id="creating-and-evaluating-an-agent">Creating and Evaluating an Agent</h2><p>To create a RAG-enabled agent with Progress Agentic RAG, you first create a knowledge box and then <a target="_blank" href="https://www.telerik.com/blogs/introductory-walk-through-progress-agentic-rag-dashboard#upload-data">load it with resources</a> (documents and web links) that ground your agent&rsquo;s responses and help reduce hallucinations.</p><p>While creating a RAG-enabled agent can reduce the chances of hallucinations by grounding responses in resources, that approach does not mean the agent&rsquo;s responses will meet your organization&rsquo;s needs. Presumably, you want to do more than just avoid bad responses. Just like the data in your organization&rsquo;s databases, it&rsquo;s your responsibility to assess your agent for reliable responses.</p><p>Specifically, you want a validation process that helps you evaluate whether your agent&rsquo;s responses are:</p><ul><li>Complete</li><li>Accurate (correct)</li><li>Relevant (to what was asked)</li><li>Explainable (does the agent&rsquo;s response make sense to the audience)</li></ul><p>Basically, this all adds up to the question from &ldquo;Marathon Man&rdquo;: <a target="_blank" href="https://www.youtube.com/watch?v=mqrpPaNm8yw">Is it safe?</a> &hellip; or, at least, reliable. Progress Agentic RAG gives you the tools to support that evaluation.</p><h2 id="a-case-study-agent">A Case Study Agent</h2><p>Your goal is <em>not</em> to create a &ldquo;universal agent&rdquo; that will answer any question. First, of course, a universal agent would be hard to validate, leaving open the possibility of your agent providing an ungrounded response to some question in the future. You want your agent to focus on a topic area where you can prove that your agent provides reliable responses.</p><p>In addition, universal agents make it hard for users to get a useful response. Because a universal agent has no defined topic area, users are forced to spend time crafting specific prompts to get responses on the topic they&rsquo;re actually interested in. And, if your users can&rsquo;t refine their prompts sufficiently, they have to sift through unrelated responses provided by the universal agent in responses to get the response they need.</p><p>Your agent is more likely to be successful if you define your agent&rsquo;s topic area. A topic area can be defined by three criteria:</p><ul><li>Who will use the agent (the agent&rsquo;s audience)</li><li>The scenarios where that audience will use the agent</li><li>The goals you and the agent&rsquo;s audience want to achieve when using your agent</li></ul><p>As a case study for this post, I created an agent for:</p><ul><li>The kitchen staff in a restaurant (audience)</li><li>To provide the kitchen staff guidance in cleaning the restaurant&rsquo;s kitchen (scenario)</li><li>To support alignment with applicable food safety regulations and company policy (goals)</li></ul><p>To support that, I created an Agentic RAG knowledge box and <a target="_blank" href="https://www.telerik.com/blogs/introductory-walk-through-progress-agentic-rag-dashboard">added as resources</a> a combination of company and government documents:</p><ul><li>A PDF version of the government &ldquo;Health and Safety Act&rdquo;</li><li>A PDF booklet from the government about food safety in commercial kitchens</li><li>A Microsoft Word document listing the makes and models of the equipment in my restaurant and the nicknames staff has assigned to them:
                <ul><li>A freezer the staff calls &ldquo;Tom&rdquo;</li><li>An oven named &ldquo;Belinda&rdquo;</li><li>A refrigerator named &ldquo;Mabel&rdquo;</li></ul></li><li>The manufacturer&rsquo;s user manuals for each piece of equipment</li><li>A link to the webpage for the section of the government&rsquo;s &ldquo;Health Protection and Promotion Act&rdquo; that covered food premises (that act, for some reason, wasn&rsquo;t available as a PDF document)</li><li>A text document containing our company&rsquo;s policies, including this section:</li></ul><blockquote><h2 id="food-safety"><strong>Food Safety</strong></h2><p>We will exceed the requirements of our regulatory body. This includes discarding any produce not used by the end of the day and replacing it the next business day. The same rule applies to any seafood or shellfish.</p></blockquote><p>I was able to create a knowledge box and add those resources in less than 15 minutes (much of that time was spent uploading government&rsquo;s PDF booklet&mdash;that thing was <em>huge</em>). When I was done, my agent looked like this:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/agentic-rag-01---restaurant-agent.png?sfvrsn=377cd9a6_2" alt="A screen shot of the home page of an Agentic RAG knowledge box called “Restaurant Kitchen”. Down the left side is a menu with opens labelled “Search” and “Advanced”" /></p><p>With your agent created, you can now validate its responses.</p><h2 id="incorporating-validation-with-experts">Incorporating Validation with Experts</h2><p>Unless your agent&rsquo;s topic area is an area where you&rsquo;re a Subject Matter Expert (a SME), your next step will be to involve the SMEs who do have the knowledge and experience to determine if your agent&rsquo;s responses are correct and complete.</p><p>Your agent starts off as a private resource that only you (or someone using your user id and password) can access. To add the SMEs who can validate your agent to your agent, surf to your agent and, in the menu on the left side of your agent, expand the Advanced section, and click the Users option at the bottom.</p><p>In the Add New User section, enter the email address for one of your SMEs and set the role you want to assign to your SME (Reader, Writer or Manager&mdash;for testing purposes, the Reader role is probably the one you want to assign to your SME). Once you&rsquo;ve done that, click the Add button to send an invitation to the SME to join your agent (you can change a user&rsquo;s role after they&rsquo;ve been added).</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/agentic-rag-02-inviting-sme.png?sfvrsn=4146b9b3_2" alt="The top part of Users screen for an Agentic RAG agent. The user has entered an email address into the textbox below the label Add new user. A dropdown list to the right of the textbox shows the three roles that can be assigned to a user: Reader, Writer, Manager " /></p><p>When your SMEs get the email, they only need to click the email&rsquo;s Finish Registration button to be brought to your Agentic RAG. Once there, they can set their password and log into your knowledge box.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/agentic-rag-03---email-invite.png?sfvrsn=2529bf13_2" alt="The email that would be received by an invited user. The email begins with a sentence in bold: You’ve been invited to join a knowledge box. Lower down, there’s a large red button with the caption Join the knowledge box." /></p><p>Now you (and any SMEs you&rsquo;ve added) can try out prompts in the agent&rsquo;s Search you think will be used with your knowledge box in order to validate the agent&rsquo;s responses.</p><p>By the way: As you select your SMEs, target people whose opinion is valued by their co-workers. That way, when you SMEs approve your agent, they can also act as champions for your agent&rsquo;s adoption.</p><h2 id="validating-for-accuracy-and-explainability">Validating for Accuracy and Explainability</h2><p>As an example, for my first test prompt, I opened my agent&rsquo;s Search page and entered, &ldquo;end of day checklist for tom.&rdquo; The response I got certainly looked correct to me (which is why you want to get SMEs involved):</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/agentic-rag-04---sample-response.png?sfvrsn=8501e129_2" alt="The Search page for an Agentic RAG agent. In the textbox at the top of the page, the user has entered “end of day checklist for tom” all in lower case. Below that is a set of bullet points detailing activities to be performed on the restaurant’s freezer. Following each bullet point are set of numbers in sequence (1, 2, 3, etc.) enclosed in light blue circles." /></p><p>If my SMEs identified an error in the response, we would use the highlighted numbers assigned to each part of my agent&rsquo;s response to determine which of my agent&rsquo;s resources generated the error. Those numbers point to the citations in the Resources section at the bottom of your agent&rsquo;s response which, in turn, show what resources were used to generate each part of the response.</p><p>Here, for example, are the resources used to generate the response for my first prompt:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/agentic-rag-05---resources.png?sfvrsn=4bf57e3e_2" alt="The bottom part of an Agentic RAG response showing where the responses were drawn from. The list shows two documents with the name of the document in bold, followed by the numbers that were used in the top part of the response. For most numbers there is some text from the document included and, occasionally, a page number. To the right of each list is a thumbnail of the document’s cover or first page, labelled “Document”." /></p><p>As the citations show, my agent drew on the relevant portion of the Food Safety booklet, my company policy and my list of kitchen equipment (that&rsquo;s how the agent recognized &ldquo;Tom&rdquo; as the freezer). Had my SMEs identified any errors in the response, we could review the related resource and either correct it or remove it (or, potentially, discover that my SME was wrong and my agent is right).</p><p>Being able to link responses to citations also validates whether a response is explainable. A response generated from relevant and authoritative resources is more likely to be accepted by your agent&rsquo;s users.</p><h2 id="validating-for-completeness">Validating for Completeness</h2><p>Your responses&rsquo; citations are also useful if your SMEs find that your agent&rsquo;s responses are accurate but incomplete.</p><p>First, you can use the citations to determine if agent has a resource that would complete the response but didn&rsquo;t use it&mdash;in other words, what resources <em>don&rsquo;t</em> appear in the citation list. If that&rsquo;s the case, on the Search page where you&rsquo;re entering your prompts, you can use the <a target="_blank" href="https://www.telerik.com/blogs/progress-agentic-rag-tuning-find-search-content">menu on the right</a> to tweak how your agent uses its resources.</p><p>In most cases, though, if your agent&rsquo;s responses are incomplete, it&rsquo;s because you&rsquo;re missing a resource. Turning on the Show Reasoning option under the Generative answer and RAG option in the menu on the right of the Search page can be helpful here. That option causes your AI to provide some insight into what your agent was looking for when assembling its response, which can help define what resources your knowledge box is missing.</p><p>Here, again, your SMEs can be helpful in identifying the missing resources you need to add (or in creating the missing resource for you to add to your agent).</p><h2 id="validating-for-relevance">Validating for Relevance</h2><p>However, you shouldn&rsquo;t take away from the discussion that adding more resources to your agent is always a good thing.</p><p>To begin with, adding more documents can interfere with providing relevant responses by expanding your agent&rsquo;s topic area. By including the user manuals for my kitchen&rsquo;s equipment, for example, I might cause my agent to veer off from food safety and into providing responses about equipment maintenance&mdash;a great response for my company&rsquo;s maintenance team but not useful to the kitchen staff I&rsquo;m targeting with this agent (the goal is <em>not</em> to create a universal agent that you would never be able to validate).</p><p>As an example, to check that my agent would stay in its topic area, I tried the prompt &ldquo;Preventative maintenance for Tom&rdquo; and got a response that, not surprisingly, focused on repair and maintenance activities. All the citations for that response pointed to the freezer&rsquo;s user manual:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/agentic-rag-06--tom-fail-maintenance.png?sfvrsn=204543d2_2" alt="The agent’s Search page with a series of bullet points detailing preventative maintenance activities for tom" /></p><p>One solution is to provide some guidance to your agent on what your agent&rsquo;s topic area is supposed to be. To do that, in the menu on the left side of your agent, under Advanced | AI Models, provide a system prompt to guide your agent in producing responses. As an example, I used &ldquo;You provide expert advice on food safety to restaurant staff&rdquo; as a system prompt.</p><p>Not all LLMs support system prompts. For those LLMs that do, you can take advantage of the Search Options | Prepend the Query option in the menu on the right side of the Search page. Any text you enter here will automatically be added to the front of every prompt and will help keep responses relevant to the agent&rsquo;s topic area. For my case study, I might use &ldquo;In terms of supporting food safety best practices&rdquo; as my prepended text.</p><p>After providing a system prompt, I tried my preventative maintenance query again and got this appropriate response from my food safety agent:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/agentic-rag-07--tom-fail-preventative-maintenance.png?sfvrsn=a9cbc76e_2" alt="The search page for the prompt “Preventative maintenance for tom” again but the list of activities has been replaced with a paragraph beginning “I’m ready to help with kitchen cleanliness and food safety but I don’t have any source blocks to cite yet.”" /></p><p>Another solution to this problem is to remove resources not relevant to the agent&rsquo;s topic area. I could, for example, remove the equipment user manuals (provided those manuals aren&rsquo;t used for any valid food safety questions, of course). I tried removing those documents from my agent&rsquo;s resources and got this message:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/agentic-rag-08--tom-fail.png?sfvrsn=2f4777dd_2" alt="The preventative maintenance Search page again. There is no response given but, in light gray text, there is the statement “The LLM can not produce an answer given the provided context”" /></p><p>That&rsquo;s not a bad response but I preferred the ones generated when I set the system prompt (you may not).</p><p>You should finish validating relevance by testing with prompts that are about the agent&rsquo;s topic area but are &ldquo;adjacent&rdquo; to information outside the topic area. For example, I tried, &ldquo;What to do with the food in Tom if it stops working&rdquo; to see if the response focused on food safety or repair and maintenance.</p><p>That prompt gave me this &ldquo;food safety related&rdquo; response indicating that my agent is staying on track even with &ldquo;maintenance adjacent&rdquo; questions:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/agentic-rag-09---tom-fail-food.png?sfvrsn=f45723c7_2" alt="The search page with the prompt “What do with the food in Tom if it stops working” followed by a set of food safety related bullet points. The first bullet point, for example, begins “Keep the freezer door closed as much as possible…” and later points discuss when to discard food." /></p><h2 id="eliminating-the-unnecessary-keeping-the-valuable">Eliminating the Unnecessary, Keeping the Valuable</h2><p>Finally, after validating your agent, you should remove any resource that isn&rsquo;t contributing to your agent&rsquo;s responses.</p><p>It&rsquo;s important to realize that you&rsquo;re not only going to be responsible for creating your agent but also for keeping your agent up to date (in my case, I&rsquo;d need to keep my agent current with changes in study food safety legislation). The more resources your agent uses, the more documents and websites you&rsquo;ll have to review and synchronize with your agent.</p><p>I&rsquo;ll put it another way: &ldquo;Extra&rdquo; resources aren&rsquo;t free. They impose a maintenance burden on you and your agent. So, if your agent isn&rsquo;t using some resource, you should eliminate it.</p><p>You can determine which resources are never used by your agent by keeping a list of your agent&rsquo;s resources and checking them off as they appear in your responses&rsquo; citations (I eliminated the equipment user manuals, for example, because once I focused my agent on food safety, my agent never used the manuals).</p><p>Reviewing the citations in your agent&rsquo;s response also allows you to determine if your agent is using the most appropriate resources in generating its responses. If you have some resource that you consider valuable that&rsquo;s being ignored in favor of a resource you think is less valuable, you can try removing the less valuable resources to see if your responses improve.</p><p>Most of my test&rsquo;s responses, for example, came from the government&rsquo;s user-friendly Food Safety booklet, while none (to my surprise) came from the regulations that actually govern restaurants and food safety. So, for my next test, I deleted the Food Safety booklet from my agent&rsquo;s resources.</p><p>With the booklet gone, my agent did switch to drawing on the legislation, but I got a <em>worse</em> response. The resulting checklist was far less specific about what kitchen staff needed to do. Given that response, I added the booklet back in.</p><h2 id="a-‘new-hire’">A &lsquo;New Hire&rsquo;</h2><p>It&rsquo;s worthwhile to think of your AI agent like a new hire in your organization. Hiring the right people is important, but so is supervising them once they join your organization to support their development. In the same way, creating a reliable AI agent is important &hellip; but so is validating it. Progress Agentic RAG gives you the tools to both create and validate your AI agent.</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">Introducing the Progress Agentic RAG .NET SDK</h4></div><div class="col-8"><p class="u-fs16 u-mb0"><a target="_blank" href="https://www.telerik.com/blogs/introducing-progress-agentic-rag-net-sdk">The .NET SDK for Progress Agentic RAG</a> provides Retrieval-Augmented Generation (RAG) capabilities to .NET development with knowledge base management, AI-powered search and resource operations. </p></div></div></aside>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:e793d386-96e8-4722-9af0-282b11768a2d</id>
    <title type="text">Practicing Vertical Slice Architecture in ASP.NET Core</title>
    <summary type="text">Tired of organizing code through technical layers? How about going beyond the basics and “slicing” the system? Vertical Slice Architecture structures the application not by the type of code to be written, but by the problem to be solved. Let’s learn how to implement it in ASP.NET Core.</summary>
    <published>2026-06-09T20:10:26Z</published>
    <updated>2026-06-18T18:00:59Z</updated>
    <author>
      <name>Assis Zang </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/practicing-vertical-slice-architecture-aspnet-core"/>
    <content type="text"><![CDATA[<p><span class="featured">Tired of organizing code through technical layers? How about going beyond the basics and &ldquo;slicing&rdquo; the system? Vertical Slice Architecture structures the application not by the type of code to be written, but by the problem to be solved. Let&rsquo;s learn how to implement it in ASP.NET Core.</span></p><p>If you&rsquo;ve ever created an API in ASP.NET Core using the classic approach of dividing it into Controllers, Services and Repositories, you&rsquo;ve probably faced some difficulties in creating the necessary structure. With each new feature, more files are scattered, more cross-dependencies appear, and it becomes more difficult to understand what truly belongs to each use case.</p><p>But what if, instead of organizing the code by technical layers, you organized it by system functionalities? That&rsquo;s exactly what Vertical Slice Architecture proposes.</p><p>Instead of a giant folder for Services and another for Repositories, each functionality becomes a vertical &ldquo;slice&rdquo; of the system, containing everything it needs: endpoint, business rules, validations, DTOs and data access. Each request then has its own isolated flow.</p><p>In this post, we&rsquo;ll explore Vertical Slice Architecture and how to apply it in a practical ASP.NET Core project.</p><h2 id="layered-architecture-and-its-trade-offs">Layered Architecture and Its Trade-offs</h2><p>Layered Architecture is one of the best-known and most widely used architectural styles in enterprise software development. It&rsquo;s commonly found in ASP.NET Core, Java/Spring and other enterprise platforms. The main point of this style is that it organizes the system into horizontal layers, each with well-defined responsibilities.</p><p>The classic architecture typically divides the system into four main layers (despite variations):</p><ol><li>Presentation (UI / API): Controllers, endpoints, user interfaces</li><li>Application / Service: Orchestrates use cases</li><li>Domain / Business: Business rules</li><li>Infrastructure / Data Access: Database, external integrations, files, etc.</li></ol><p>Each layer depends only on the layer immediately below it (or on abstractions), creating a predictable flow of dependencies.</p><p>Despite its popularity, this approach has some drawbacks, such as flow coupling. Although the layers are separate, use cases traverse all of them: Controller -&gt; Service -&gt; Domain -&gt; Repository -&gt; Database. This creates structural flow coupling.</p><p>A simple change in one use case can require changes in multiple layers. This results in scattered files and constant navigation between projects/folders to find important parts of the code.</p><p>Another negative point is that this architecture organizes code by technical type, not by functionality. For example, if you want to understand the &ldquo;Create Order&rdquo; use case, you will need to open multiple files in different folders (Controllers, Services folder, Repositories folder, etc.).</p><p>When can Layered Architecture start to become a problem? In large systems with many use cases, a high volume of changes per feature, a complex domain that requires strong modeling, and large teams working on multiple fronts. In scenarios like this, an excellent alternative is Vertical Slice Architecture.</p><h2 id="vertical-slice-architecture-as-an-alternative">Vertical Slice Architecture as an Alternative</h2><p>Vertical Slice Architecture is an architectural style where the system is designed and organized by features (functionalities), rather than by technical layers as in traditional layer-based approaches.</p><p>Vertical Slice emerged as an alternative to problems with traditional approaches, such as the difficulty in maintenance due to classes being scattered throughout the program.</p><p>The proposal of Vertical Slice is simple: organize the code by functionality (feature) and not by technical type. Imagine you need to create functionalities for creating and canceling an order.</p><p>In a layered approach, we would have the standard structure:</p><pre><code>Controller 
- OrderController 
Service 
- OrderService
Repository 
- OrderRepository
Dtos 
- OrderDto
</code></pre><p>In a Vertical Slice Architecture, you would organize things by use case:</p><pre><code>Features/
CreateOrder/
CreateOrderEndpoint.cs
CreateOrderHandler.cs
CreateOrderRequest.cs
CreateOrderValidator.cs

CancelOrder/
CancelOrderEndpoint.cs
CancelOrderHandler.cs
CancelOrderRequest.cs
CancelOrderValidator.cs
</code></pre><p>Much clearer now, isn&rsquo;t it? The image below shows a simple comparison between Vertical Slice Architecture and Layered Architecture:</p><p><img title="vertical slice vs layered" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/vertical-slice-vs-layered.png?sfvrsn=743f49ce_2" alt="Vertical Slice VS Layered" /></p><h2 id="implementing-vertical-slice-architecture">Implementing Vertical Slice Architecture</h2><p>Now we&rsquo;re going to create an API using Vertical Slice Architecture. Our API will be used to manage a subscription service. You can access the complete code in this GitHub repository: <a target="_blank" href="https://github.com/zangassis/vertical-slice-subs">Vertical Slice Subs Source Code</a>.</p><p>Our project will follow this structure:</p><p><img title="project structure" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/project-structure.png?sfvrsn=10deb58f_2" alt="Project structure" /></p><h3 id="creating-the-domain-model">Creating the Domain Model</h3><p>To create the base application, you can use the following command in the terminal:</p><pre class=" language-bash"><code class="prism  language-bash">dotnet new web -o SubscriptionManagement
</code></pre><p>Then, inside the project, create a new folder called &ldquo;Features,&rdquo; and inside it another folder called &ldquo;Subscriptions&rdquo;. Add the following class and enum to the folder Subscriptions:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">namespace</span> SubscriptionManagement<span class="token punctuation">.</span>Features<span class="token punctuation">.</span>Subscriptions<span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Subscription</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> Guid Id <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 operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</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">string</span> ServiceName <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 operator">=</span> <span class="token keyword">default</span><span class="token operator">!</span><span class="token punctuation">;</span>
    <span class="token keyword">public</span> <span class="token keyword">decimal</span> Price <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> DateOnly StartDate <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> DateOnly<span class="token operator">?</span> TrialEndsAt <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> SubscriptionStatus Status <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">private</span> <span class="token function">Subscription</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 function">Subscription</span><span class="token punctuation">(</span><span class="token keyword">string</span> serviceName<span class="token punctuation">,</span> <span class="token keyword">decimal</span> price<span class="token punctuation">,</span> DateOnly startDate<span class="token punctuation">,</span> DateOnly<span class="token operator">?</span> trialEndsAt<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        ServiceName <span class="token operator">=</span> serviceName<span class="token punctuation">;</span>
        Price <span class="token operator">=</span> price<span class="token punctuation">;</span>
        StartDate <span class="token operator">=</span> startDate<span class="token punctuation">;</span>
        TrialEndsAt <span class="token operator">=</span> trialEndsAt<span class="token punctuation">;</span>
        Status <span class="token operator">=</span> SubscriptionStatus<span class="token punctuation">.</span>Active<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">Cancel</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>Status <span class="token operator">==</span> SubscriptionStatus<span class="token punctuation">.</span>Canceled<span class="token punctuation">)</span>
            <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Subscription already canceled."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        Status <span class="token operator">=</span> SubscriptionStatus<span class="token punctuation">.</span>Canceled<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> SubscriptionManagement<span class="token punctuation">.</span>Features<span class="token punctuation">.</span>Subscriptions<span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">enum</span> SubscriptionStatus
<span class="token punctuation">{</span>
    Active <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">,</span>
    Canceled <span class="token operator">=</span> <span class="token number">2</span>
<span class="token punctuation">}</span>
</code></pre><p>The next step is to create the database related files. In the root of the project, create a new folder called &ldquo;Infrastructure&rdquo; and add the following class to it:</p><ul><li>AppDbContext</li></ul><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">using</span> SubscriptionManagement<span class="token punctuation">.</span>Features<span class="token punctuation">.</span>Subscriptions<span class="token punctuation">;</span>

<span class="token keyword">namespace</span> SubscriptionManagement<span class="token punctuation">.</span>Infrastructure<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> DbSet<span class="token operator">&lt;</span>Subscription<span class="token operator">&gt;</span> Subscriptions <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token generic-method function">Set<span class="token punctuation">&lt;</span>Subscription<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">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 punctuation">}</span>
</code></pre><h2 id="-feature-1-createsubscription"> Feature 1: CreateSubscription</h2><h3 id="command">Command</h3><p>Our first feature will be used to create a new subscription. Inside the <code>Features/Subscriptions/</code> folder, create a new folder called &ldquo;CreateSubscriptions&rdquo; (all the classes below should be created within this structure: <code>Features/Subscriptions/CreateSubscriptions</code>), and inside it, add the following record:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> record <span class="token function">CreateSubscriptionCommand</span><span class="token punctuation">(</span>
    <span class="token keyword">string</span> ServiceName<span class="token punctuation">,</span>
    <span class="token keyword">decimal</span> Price<span class="token punctuation">,</span>
    DateOnly StartDate<span class="token punctuation">,</span>
    DateOnly<span class="token operator">?</span> TrialEndsAt
<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><h3 id="validator">Validator</h3><p>Next, let&rsquo;s create a validator. Create the class below:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">using</span> FluentValidation<span class="token punctuation">;</span>

<span class="token keyword">namespace</span> SubscriptionManagement<span class="token punctuation">.</span>Features<span class="token punctuation">.</span>Subscriptions<span class="token punctuation">.</span>CreateSubscriptions<span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CreateSubscriptionValidator</span> <span class="token punctuation">:</span> AbstractValidator<span class="token operator">&lt;</span>CreateSubscriptionCommand<span class="token operator">&gt;</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token function">CreateSubscriptionValidator</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token function">RuleFor</span><span class="token punctuation">(</span>x <span class="token operator">=</span><span class="token operator">&gt;</span> x<span class="token punctuation">.</span>ServiceName<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">NotEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">MaximumLength</span><span class="token punctuation">(</span><span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token function">RuleFor</span><span class="token punctuation">(</span>x <span class="token operator">=</span><span class="token operator">&gt;</span> x<span class="token punctuation">.</span>Price<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">GreaterThan</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token function">RuleFor</span><span class="token punctuation">(</span>x <span class="token operator">=</span><span class="token operator">&gt;</span> x<span class="token punctuation">.</span>TrialEndsAt<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">GreaterThan</span><span class="token punctuation">(</span>x <span class="token operator">=</span><span class="token operator">&gt;</span> x<span class="token punctuation">.</span>StartDate<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">When</span><span class="token punctuation">(</span>x <span class="token operator">=</span><span class="token operator">&gt;</span> x<span class="token punctuation">.</span>TrialEndsAt<span class="token punctuation">.</span>HasValue<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><h3 id="handler">Handler</h3><p>The concept of a Handler is common in projects that use Vertical Slice Architecture. A Handler is the component responsible for executing the application&rsquo;s use case&mdash;that is, it contains the logic of the functionality.</p><p>Typically, the Handler is the class or function that receives a command or query and executes the corresponding operation, coordinating business rules, database access, calls to other services and response return.</p><p>Handlers work well in architectures that use Command Query Responsibility Segregation (CQRS), especially when using MediatR. Here, the concept doesn&rsquo;t depend on MediatR; it already exists simply with the organization by slices.</p><p>Now, let&rsquo;s create our first Handler. To do this, create the classes below:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">namespace</span> SubscriptionManagement<span class="token punctuation">.</span>Features<span class="token punctuation">.</span>Subscriptions<span class="token punctuation">.</span>CreateSubscriptions<span class="token punctuation">;</span>

<span class="token keyword">public</span> record <span class="token function">CreateSubscriptionCommand</span><span class="token punctuation">(</span>
    <span class="token keyword">string</span> ServiceName<span class="token punctuation">,</span>
    <span class="token keyword">decimal</span> Price<span class="token punctuation">,</span>
    DateOnly StartDate<span class="token punctuation">,</span>
    DateOnly<span class="token operator">?</span> TrialEndsAt
<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> SubscriptionManagement<span class="token punctuation">.</span>Infrastructure<span class="token punctuation">;</span>

<span class="token keyword">namespace</span> SubscriptionManagement<span class="token punctuation">.</span>Features<span class="token punctuation">.</span>Subscriptions<span class="token punctuation">.</span>CreateSubscriptions<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">CreateSubscriptionHandler</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">async</span> Task<span class="token operator">&lt;</span>IResult<span class="token operator">&gt;</span> <span class="token function">Handle</span><span class="token punctuation">(</span>CreateSubscriptionCommand command<span class="token punctuation">,</span> AppDbContext dbContext<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">var</span> subscription <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Subscription</span><span class="token punctuation">(</span>
            command<span class="token punctuation">.</span>ServiceName<span class="token punctuation">,</span>
            command<span class="token punctuation">.</span>Price<span class="token punctuation">,</span>
            command<span class="token punctuation">.</span>StartDate<span class="token punctuation">,</span>
            command<span class="token punctuation">.</span>TrialEndsAt
        <span class="token punctuation">)</span><span class="token punctuation">;</span>

        dbContext<span class="token punctuation">.</span>Subscriptions<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span>subscription<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">await</span> dbContext<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">return</span> Results<span class="token punctuation">.</span><span class="token function">Created</span><span class="token punctuation">(</span>$<span class="token string">"/subscriptions/{subscription.Id}"</span><span class="token punctuation">,</span> subscription<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Note that the handler class has a method to create a new subscription, and returns an HTTP <code>Created</code> status.</p><h3 id="endpoint">Endpoint</h3><p>In Vertical Slice Architecture, it&rsquo;s also common to declare separate endpoints. In this case, we&rsquo;ll have one endpoint for each use case. Create the following class:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">using</span> FluentValidation<span class="token punctuation">;</span>
<span class="token keyword">using</span> SubscriptionManagement<span class="token punctuation">.</span>Infrastructure<span class="token punctuation">;</span>

<span class="token keyword">namespace</span> SubscriptionManagement<span class="token punctuation">.</span>Features<span class="token punctuation">.</span>Subscriptions<span class="token punctuation">.</span>CreateSubscriptions<span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CreateSubscriptionEndpoint</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">Map</span><span class="token punctuation">(</span>WebApplication app<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        app<span class="token punctuation">.</span><span class="token function">MapPost</span><span class="token punctuation">(</span><span class="token string">"/subscriptions"</span><span class="token punctuation">,</span> 
            <span class="token keyword">async</span> <span class="token punctuation">(</span>CreateSubscriptionCommand command<span class="token punctuation">,</span> 
                AppDbContext dbContext<span class="token punctuation">,</span> 
                IValidator<span class="token operator">&lt;</span>CreateSubscriptionCommand<span class="token operator">&gt;</span> validator<span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">&gt;</span>
            <span class="token punctuation">{</span>
                <span class="token keyword">var</span> validation <span class="token operator">=</span> <span class="token keyword">await</span> validator<span class="token punctuation">.</span><span class="token function">ValidateAsync</span><span class="token punctuation">(</span>command<span class="token punctuation">)</span><span class="token punctuation">;</span>

                <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>validation<span class="token punctuation">.</span>IsValid<span class="token punctuation">)</span>
                    <span class="token keyword">return</span> Results<span class="token punctuation">.</span><span class="token function">ValidationProblem</span><span class="token punctuation">(</span>validation<span class="token punctuation">.</span><span class="token function">ToDictionary</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> CreateSubscriptionHandler<span class="token punctuation">.</span><span class="token function">Handle</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> dbContext<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>Here we are defining an endpoint for creating a new subscription. It starts by validating the data, and if it&rsquo;s valid, it uses the handler to create the new record and return the expected status.</p><h2 id="-feature-2-cancelsubscription"> Feature 2: CancelSubscription</h2><p>The subscription creation flow is ready. Now let&rsquo;s consider the cancellation use case. Following the same logic as before, within the <code>Features/Subscriptions</code> structure, create a new folder called <code>CancelSubscriptions</code> and add the following classes to that folder:</p><h3 id="command-1">Command</h3><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">namespace</span> SubscriptionManagement<span class="token punctuation">.</span>Features<span class="token punctuation">.</span>Subscriptions<span class="token punctuation">.</span>CancelSubscriptions<span class="token punctuation">;</span>

<span class="token keyword">public</span> record <span class="token function">CancelSubscriptionCommand</span><span class="token punctuation">(</span>Guid Id<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><h3 id="handler-1">Handler</h3><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">using</span> SubscriptionManagement<span class="token punctuation">.</span>Infrastructure<span class="token punctuation">;</span>

<span class="token keyword">namespace</span> SubscriptionManagement<span class="token punctuation">.</span>Features<span class="token punctuation">.</span>Subscriptions<span class="token punctuation">.</span>CancelSubscriptions<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">CancelSubscriptionHandler</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">async</span> Task<span class="token operator">&lt;</span>IResult<span class="token operator">&gt;</span> <span class="token function">Handle</span><span class="token punctuation">(</span>
        CancelSubscriptionCommand command<span class="token punctuation">,</span>
        AppDbContext dbContext<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">var</span> subscription <span class="token operator">=</span> <span class="token keyword">await</span> dbContext<span class="token punctuation">.</span>Subscriptions
            <span class="token punctuation">.</span><span class="token function">FirstOrDefaultAsync</span><span class="token punctuation">(</span>x <span class="token operator">=</span><span class="token operator">&gt;</span> x<span class="token punctuation">.</span>Id <span class="token operator">==</span> command<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>subscription <span class="token keyword">is</span> <span class="token keyword">null</span><span class="token punctuation">)</span>
            <span class="token keyword">return</span> Results<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 keyword">try</span>
        <span class="token punctuation">{</span>
            subscription<span class="token punctuation">.</span><span class="token function">Cancel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token keyword">await</span> dbContext<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">catch</span> <span class="token punctuation">(</span><span class="token class-name">InvalidOperationException</span> ex<span class="token punctuation">)</span>
        <span class="token punctuation">{</span>
            <span class="token keyword">return</span> Results<span class="token punctuation">.</span><span class="token function">BadRequest</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token punctuation">{</span> error <span class="token operator">=</span> ex<span class="token punctuation">.</span>Message <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> Results<span class="token punctuation">.</span><span class="token function">NoContent</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><h3 id="endpoint-1">Endpoint</h3><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">using</span> SubscriptionManagement<span class="token punctuation">.</span>Infrastructure<span class="token punctuation">;</span>

<span class="token keyword">namespace</span> SubscriptionManagement<span class="token punctuation">.</span>Features<span class="token punctuation">.</span>Subscriptions<span class="token punctuation">.</span>CancelSubscriptions<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">CancelSubscriptionEndpoint</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">Map</span><span class="token punctuation">(</span>WebApplication app<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        app<span class="token punctuation">.</span><span class="token function">MapDelete</span><span class="token punctuation">(</span><span class="token string">"/subscriptions/{id:guid}"</span><span class="token punctuation">,</span>
            <span class="token keyword">async</span> <span class="token punctuation">(</span>Guid id<span class="token punctuation">,</span> AppDbContext dbContext<span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">&gt;</span>
            <span class="token punctuation">{</span>
                <span class="token keyword">var</span> command <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">CancelSubscriptionCommand</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token keyword">return</span> <span class="token keyword">await</span> CancelSubscriptionHandler<span class="token punctuation">.</span><span class="token function">Handle</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> dbContext<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><h3 id="program-class">Program Class</h3><p>The final step is to configure the Program class to use the endpoints we created above, to save time, we&rsquo;ll use an in-memory database. So add the following code to the Program class:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">using</span> FluentValidation<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">using</span> SubscriptionManagement<span class="token punctuation">.</span>Features<span class="token punctuation">.</span>Subscriptions<span class="token punctuation">.</span>CancelSubscriptions<span class="token punctuation">;</span>
<span class="token keyword">using</span> SubscriptionManagement<span class="token punctuation">.</span>Features<span class="token punctuation">.</span>Subscriptions<span class="token punctuation">.</span>CreateSubscriptions<span class="token punctuation">;</span>
<span class="token keyword">using</span> SubscriptionManagement<span class="token punctuation">.</span>Infrastructure<span class="token punctuation">;</span>

<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>

builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token generic-method function">AddDbContext<span class="token punctuation">&lt;</span>AppDbContext<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span>opt <span class="token operator">=</span><span class="token operator">&gt;</span>
    opt<span class="token punctuation">.</span><span class="token function">UseInMemoryDatabase</span><span class="token punctuation">(</span><span class="token string">"subscriptions-db"</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">AddValidatorsFromAssemblyContaining<span class="token punctuation">&lt;</span>CreateSubscriptionValidator<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> 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>

CreateSubscriptionEndpoint<span class="token punctuation">.</span><span class="token function">Map</span><span class="token punctuation">(</span>app<span class="token punctuation">)</span><span class="token punctuation">;</span>
CancelSubscriptionEndpoint<span class="token punctuation">.</span><span class="token function">Map</span><span class="token punctuation">(</span>app<span class="token punctuation">)</span><span class="token punctuation">;</span>

app<span class="token punctuation">.</span><span class="token function">Run</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>Note that the Program class is quite simple, only creating configurations such as the use of the in-memory database, validations and the calling of the endpoint methods: <code>CreateSubscriptionEndpoint.Map(app);</code> and <code>CancelSubscriptionEndpoint.Map(app);</code>.</p><h2 id="-testing-the-application"> Testing the Application</h2><p>Now, let&rsquo;s run the endpoints and verify that the application is functional.</p><p>Run the application and make a POST request to the route <code>http://localhost:5127/subscriptions</code> using the JSON below as the body:</p><pre class=" language-json"><code class="prism  language-json"><span class="token punctuation">{</span>
  <span class="token string">"serviceName"</span><span class="token punctuation">:</span> <span class="token string">"Aspflix"</span><span class="token punctuation">,</span>
  <span class="token string">"price"</span><span class="token punctuation">:</span> <span class="token number">39.90</span><span class="token punctuation">,</span>
  <span class="token string">"startDate"</span><span class="token punctuation">:</span> <span class="token string">"2026-03-01"</span><span class="token punctuation">,</span>
  <span class="token string">"trialEndsAt"</span><span class="token punctuation">:</span> <span class="token string">"2026-03-15"</span>
<span class="token punctuation">}</span>
</code></pre><p>If everything goes well, you will receive the following response in a debugger like Progress Telerik <a target="_blank" href="https://www.telerik.com/fiddler/fiddler-everywhere">Fiddler Everywhere</a>:</p><p><img title="create a subscription" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/create-a-subscription.png?sfvrsn=29b0495f_2" alt="Create a subscription" /></p><p>And to test the cancellation, simply execute a DELETE request to the route: <code>http://localhost:5127/subscriptions/ID_HERE</code>.</p><p>And you will receive the following response:</p><p><img title="cancel a subscription" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/cancel-a-subscription.png?sfvrsn=7b047aec_2" alt="Cancel a subscription" /></p><p>If you try to cancel again, you will receive a validation error.</p><p><img title="cancel a subscription error" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/cancel-a-subscription-error.png?sfvrsn=197162f9_2" alt="Cancel a subscription error" /></p><h2 id="conclusion">Conclusion</h2><p>Vertical Slice Architecture offers an alternative approach focused on system functionalities, rather than predefined structures.</p><p>As we saw in the article, this brings advantages such as ease of maintenance, due to the low cognitive requirements for understanding the project. It also offers agility in finding functions, as business rules are separated by use case. Furthermore, we implemented a subscription project in ASP.NET Core using Slice Architecture.</p><p>I hope this post helps you decide which architectural approach to use when creating or refactoring your projects.</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">Creating More Realistic Tests with In-Memory Databases in ASP.NET Core
</h4></div><div class="col-8"><p class="u-fs16 u-mb0"><a target="_blank" href="https://www.telerik.com/blogs/creating-more-realistic-tests-memory-databases-aspnet-core">Testing ASP.NET Core APIs with in-memory</a> SQLite and JustMock enables validation of real-world scenarios like pagination, keys and rules without a real database.</p></div></div></aside>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:ab0667e5-0b14-47dd-ac74-880d6313ec8e</id>
    <title type="text">Data Fetching with TanStack Query for Vue</title>
    <summary type="text">Learn how to set up TanStack Query in a Vue app, fetch data with useQuery, leverage automatic caching and control refetching behavior.</summary>
    <published>2026-06-09T17:26:45Z</published>
    <updated>2026-06-18T18:00:59Z</updated>
    <author>
      <name>Marina Mosti </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/data-fetching-tanstack-query-vue"/>
    <content type="text"><![CDATA[<p><span class="featured">Learn how to set up TanStack Query in a Vue app, fetch data with useQuery, leverage automatic caching and control refetching behavior.</span></p><p>If you have been building Vue applications for any amount of time, you&rsquo;ve likely run into the same pattern over and over: call <code>fetch</code> or <code>axios</code> inside <code>onMounted</code>, store the result in a <code>ref</code>, manually track whether the request is loading, handle errors, and then figure out when and how to refetch the data when it goes stale. It works, but it doesn&rsquo;t scale well, and it quickly becomes a mess of boilerplate scattered across your components.</p><p>In my previous article on <a target="_blank" href="https://www.telerik.com/blogs/vue-basics-pinia-state-management">Pinia state management</a>, I mentioned TanStack Query as a powerful tool for managing asynchronous state that comes from your API. In this article, we&rsquo;ll explore how to set it up in a Vue app, fetch data with <code>useQuery</code>, leverage automatic caching and control refetching behavior.</p><h2 id="what-is-tanstack-query">What Is TanStack Query?</h2><p><a target="_blank" href="https://tanstack.com/query/latest">TanStack Query</a> (which curiously enough started as a React lib, React Query) is an async state management library that has since become framework-agnostic, with first class support for Vue. It is specifically designed to manage data that lives on your API and needs to be fetched, cached, synchronized and updated.</p><p>TanStack Query gives you out-of-the-box automatic caching, background refetching, request deduplication, loading and error states&mdash;all of this for free with the basic hands-off configuration.</p><p>It&rsquo;s important to understand when to reach for TanStack Query vs. something like Pinia. Pinia is excellent for managing client state, things like UI preferences, form data, or any state that originates and lives entirely in the browser.</p><p>TanStack Query, on the other hand, shines when dealing with server state, data that comes from an API and that other users or processes could change at any time. They complement each other rather than compete.</p><p>Big disclaimer though: I highly recommend not mixing the two. I&rsquo;ve yet to find a production grade application where I needed to use Pinia in conjuction with Tanstack. If you do end up needing both, make sure that you don&rsquo;t overlap them. Keep your client and server data separate or it can become spaghetti code very quickly.</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">Vue Basics: State Management in Vue</h4></div><div class="col-8"><p class="u-fs16 u-mb0">Learn how to <a target="_blank" href="https://www.telerik.com/blogs/vue-basics-state-management-vue">scale Vue state management</a> with ref/reactive, props/emits, provide/inject and Pinia as your app grows.</p></div></div><hr class="u-mb3" /></aside><h2 id="setting-up-tanstack-query">Setting Up TanStack Query</h2><p>Setting up TanStack Query for a Vue 3 application is straightforward. First, install the package:</p><pre><code>npm install @tanstack/vue-query
# or
yarn add @tanstack/vue-query
</code></pre><p>Then, head over to your <code>main.js</code> (or wherever you are mounting your app) and register the plugin.</p><p><strong>main.js</strong></p><pre class=" language-javascript"><code class="prism  language-javascript"><span class="token keyword">import</span> <span class="token punctuation">{</span> createApp <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'vue'</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> VueQueryPlugin <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@tanstack/vue-query'</span>
<span class="token keyword">import</span> App <span class="token keyword">from</span> <span class="token string">'./App.vue'</span>

<span class="token keyword">const</span> app <span class="token operator">=</span> <span class="token function">createApp</span><span class="token punctuation">(</span>App<span class="token punctuation">)</span>

app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span>VueQueryPlugin<span class="token punctuation">)</span>

app<span class="token punctuation">.</span><span class="token function">mount</span><span class="token punctuation">(</span><span class="token string">'#app'</span><span class="token punctuation">)</span>
</code></pre><p>That&rsquo;s it! Under the hood, <code>VueQueryPlugin</code> creates a <code>QueryClient</code> for us and provides it to the entire application. If you need to customize default options, you can pass a configuration object to the plugin.</p><p>In the example below, we can set a global <code>staleTime</code> (the time that it takes the application to consider data that is already pulled &ldquo;stale&rdquo; and, thus, marks it for refetch).</p><pre class=" language-javascript"><code class="prism  language-javascript">app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span>VueQueryPlugin<span class="token punctuation">,</span> <span class="token punctuation">{</span>
  queryClientConfig<span class="token punctuation">:</span> <span class="token punctuation">{</span>
    defaultOptions<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      queries<span class="token punctuation">:</span> <span class="token punctuation">{</span>
        staleTime<span class="token punctuation">:</span> <span class="token number">1000</span> <span class="token operator">*</span> <span class="token number">60</span> <span class="token operator">*</span> <span class="token number">5</span><span class="token punctuation">,</span> <span class="token comment">// 5 minutes</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>With the plugin registered, we are now ready to start fetching data.</p><h2 id="your-first-query-with-usequery">Your First Query with useQuery</h2><p>The core of TanStack Query is the <code>useQuery</code> composable. It takes a configuration object and returns a set of reactive properties that we can use directly in our templates. Let&rsquo;s build a simple component that fetches a list of users.</p><p><strong>UserList.vue</strong></p><pre class=" language-vue"><code class="prism  language-vue">&lt;script setup&gt;
import { useQuery } from '@tanstack/vue-query'

const fetchUsers = async () =&gt; {
  const response = await fetch('https://myapp.com/users')
  if (!response.ok) {
    throw new Error('Failed to fetch users')
  }
  return response.json()
}

const { data, isLoading, isError, error } = useQuery({
  queryKey: ['users'],
  queryFn: fetchUsers,
})
&lt;/script&gt;

&lt;template&gt;
  &lt;p v-if="isLoading"&gt;Loading users...&lt;/p&gt;
  &lt;p v-else-if="isError"&gt;Something went wrong: {{ error.message }}&lt;/p&gt;
  &lt;ul v-else&gt;
    &lt;li v-for="user in data" :key="user.id"&gt;
      {{ user.name }}
    &lt;/li&gt;
  &lt;/ul&gt;
&lt;/template&gt;
</code></pre><p>Let&rsquo;s break this down. The <code>useQuery</code> composable receives an object with two essential properties:</p><ul><li><code>queryKey</code>: An array that uniquely identifies this query. TanStack Query uses this key to cache, deduplicate and refetch the data. Think of it as a unique ID for this particular piece of server state.</li><li><code>queryFn</code>: An async function that actually fetches the data. It <strong>must</strong> return a promise that resolves with the data or throws an error.</li></ul><p>The composable returns several reactive properties, but the ones you&rsquo;ll use most often are:</p><ul><li><code>data</code>: The resolved data from the query function. It&rsquo;s <code>undefined</code> until the query resolves!</li><li><code>isLoading</code>: A boolean that is <code>true</code> while the query is fetching for the <em>first time</em> (no cached data exists yet).</li><li><code>isFetching</code>: A boolean that is <code>true</code> while the query is fetching. This includes background refetches.</li><li><code>isError</code>: A boolean that is <code>true</code> if the query encountered an error.</li><li><code>error</code>: The error object if the query failed.</li></ul><p>Notice how clean our template is. We don&rsquo;t need to manually set up loading flags or try/catch blocks. TanStack Query handles all of that for us.</p><p>I highly recommend that you read through the <a target="_blank" href="https://tanstack.com/query/latest/docs/framework/react/guides/queries">Query basics</a> page from the Tanstack documentation to really understand the different states and how to manage them.</p><h3 id="query-keys">Query Keys</h3><p>Query keys deserve a bit more attention. They are not just static identifiers; they can be dynamic. For example, if we wanted to fetch a single user by ID, we could write:</p><pre class=" language-javascript"><code class="prism  language-javascript"><span class="token keyword">const</span> <span class="token punctuation">{</span> data <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">useQuery</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  queryKey<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">'user'</span><span class="token punctuation">,</span> userId<span class="token punctuation">]</span><span class="token punctuation">,</span>
  queryFn<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token function">fetchUserById</span><span class="token punctuation">(</span>userId<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
</code></pre><p>Whenever <code>userId</code> changes, TanStack Query will automatically recognize it as a <em>different</em> query and fetch the new data accordingly. If the data for that particular key was already cached, it will be returned instantly while a background refetch happens. This is extremely powerful and removes a lot of the manual <code>watch</code> logic you would otherwise have to write.</p><p>If you&rsquo;re having a hard time wrapping your head around this one, think about what an API endpoint usually looks like:</p><ul><li><code>myapp.com/user/1</code></li><li><code>myApp.com/user/2</code></li></ul><p>The <code>user</code> portion of the <code>queryKey</code> closely matches our API endpoint syntax (this doesn&rsquo;t have to be like this, this is merely a suggestion). More importantly, the <code>userId</code> reactive value will represent the ID (1/2) that we are sending to the API. We definitely want the caches for both of the calls above to be <em>different</em> from one another.</p><h2 id="caching-and-stale-data">Caching and Stale Data</h2><p>One of the biggest advantages of TanStack Query is its built-in caching. Once a query resolves, the result is stored in memory and associated with its query key. If another component (or the same component after a navigation) requests data with the same query key, the cached data is returned immediately (if it&rsquo;s not stale), so no other call has to be made to the API.</p><p>This is particularly noticeable when navigating between pages. Say you have a user list on one page and a user detail on another. When your user navigates back to the list, the data will already be there. No loading spinner, no flash of empty content.</p><p>TanStack Query manages this through two important concepts:</p><ul><li><code>staleTime</code>: How long (in milliseconds) the data is considered &ldquo;fresh.&rdquo; While data is fresh, TanStack Query will <em>never</em> refetch it. The default is <code>0</code>, meaning data is immediately considered stale after fetching.</li><li><code>gcTime</code> (garbage collection time): How long unused cached data is kept in memory before being garbage collected. The default is 5 minutes.</li></ul><p>You can configure these per query:</p><pre class=" language-javascript"><code class="prism  language-javascript"><span class="token keyword">const</span> <span class="token punctuation">{</span> data <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">useQuery</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  queryKey<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">'users'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  queryFn<span class="token punctuation">:</span> fetchUsers<span class="token punctuation">,</span>
  staleTime<span class="token punctuation">:</span> <span class="token number">1000</span> <span class="token operator">*</span> <span class="token number">60</span> <span class="token operator">*</span> <span class="token number">10</span><span class="token punctuation">,</span> <span class="token comment">// 10 minutes</span>
  gcTime<span class="token punctuation">:</span> <span class="token number">1000</span> <span class="token operator">*</span> <span class="token number">60</span> <span class="token operator">*</span> <span class="token number">30</span><span class="token punctuation">,</span>    <span class="token comment">// 30 minutes</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
</code></pre><p>In this example, after the data is first fetched, it will be considered fresh for 10 minutes. During that time, any component mounting with the same <code>['users']</code> query key will get the cached data instantly with zero network requests. After 10 minutes, the data is marked stale and will be refetched the next time it is needed. If no component uses the <code>['users']</code> query for 30 minutes, the cache entry is garbage collected entirely.</p><p>To be quite honest with you, I&rsquo;ve personally never bother setting or modifying the default <code>gcTime</code>, but it&rsquo;s an important concept to keep in mind when using Tanstack.</p><h2 id="refetching">Refetching</h2><p>TanStack Query doesn&rsquo;t just fetch your data once and forget about it. It comes with several mechanisms to keep your data up to date, both automatic and manual.</p><h3 id="automatic-refetching">Automatic Refetching</h3><p>By default, TanStack Query will automatically refetch stale queries in the following situations:</p><ul><li><strong>Window focus</strong>: When the user leaves your app and comes back (tabs away and returns), stale queries are refetched. This is incredibly useful and one of my favorite benefits of Tanstack that you get for free&mdash;your users always see fresh data without lifting a finger.</li><li><strong>Network reconnect</strong>: If the user loses their internet connection and reconnects, stale queries are refetched automatically.</li></ul><p>You can control these behaviors individually:</p><pre class=" language-javascript"><code class="prism  language-javascript"><span class="token keyword">const</span> <span class="token punctuation">{</span> data <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">useQuery</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  queryKey<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">'users'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  queryFn<span class="token punctuation">:</span> fetchUsers<span class="token punctuation">,</span>
  refetchOnWindowFocus<span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token comment">// disable window focus refetch</span>
  refetchOnReconnect<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>    <span class="token comment">// this is the default</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
</code></pre><h3 id="polling-with-refetchinterval">Polling with refetchInterval</h3><p>For data that needs to be updated on a regular cadence, for example a notifications counter or a live feed, you can set up polling with <code>refetchInterval</code>.</p><pre class=" language-javascript"><code class="prism  language-javascript"><span class="token keyword">const</span> <span class="token punctuation">{</span> data <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">useQuery</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  queryKey<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">'notifications'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  queryFn<span class="token punctuation">:</span> fetchNotifications<span class="token punctuation">,</span>
  refetchInterval<span class="token punctuation">:</span> <span class="token number">1000</span> <span class="token operator">*</span> <span class="token number">30</span><span class="token punctuation">,</span> <span class="token comment">// refetch every 30 seconds</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
</code></pre><p>This will fire a background refetch every 30 seconds regardless of user interaction and regardless of stale state. The UI updates seamlessly since the cached data is replaced when the new data arrives. Keep in mind that all <code>data</code> returned by <code>useQuery</code> is reactive.</p><h3 id="manual-refetching">Manual Refetching</h3><p>Sometimes you want to give the user explicit control over when to refresh data. The <code>useQuery</code> composable returns a <code>refetch</code> function for exactly this purpose.</p><p><strong>UserList.vue</strong></p><pre class=" language-vue"><code class="prism  language-vue">&lt;script setup&gt;
import { useQuery } from '@tanstack/vue-query'

const { data, isLoading, refetch, isFetching } = useQuery({
  queryKey: ['users'],
  queryFn: fetchUsers,
})
&lt;/script&gt;

&lt;template&gt;
  &lt;button @click="refetch" :disabled="isFetching"&gt;
    Refresh
  &lt;/button&gt;

  &lt;p v-if="isLoading"&gt;Loading users...&lt;/p&gt;
  &lt;ul v-else&gt;
    &lt;li v-for="user in data" :key="user.id"&gt;
      {{ user.name }}
    &lt;/li&gt;
  &lt;/ul&gt;
&lt;/template&gt;
</code></pre><p>Notice we&rsquo;re using <code>isFetching</code> here instead of <code>isLoading</code> to control the button&rsquo;s disabled state. The distinction is important: <code>isLoading</code> is only <code>true</code> when there is <em>no cached data</em> and a fetch is in progress (first load), while <code>isFetching</code> is <code>true</code> whenever <em>any</em> fetch is happening, including background refetches. This allows us to show the cached data in the list while indicating that a refresh is in progress.</p><h3 id="conditional-queries-with-enabled">Conditional Queries with Enabled</h3><p>One last option worth mentioning is <code>enabled</code>. Sometimes you don&rsquo;t want a query to fire immediately. Perhaps you need to wait for a user to select something first, or you want to set up a <code>computed</code> property with some conditions that need to occur first.</p><pre class=" language-javascript"><code class="prism  language-javascript"><span class="token keyword">const</span> <span class="token punctuation">{</span> data <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">useQuery</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  queryKey<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">'users'</span><span class="token punctuation">,</span> userId<span class="token punctuation">]</span><span class="token punctuation">,</span>
  queryFn<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token function">fetchUserById</span><span class="token punctuation">(</span>userId<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token punctuation">,</span>
  enabled<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token operator">!</span><span class="token operator">!</span>userId<span class="token punctuation">.</span>value<span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
</code></pre><p>The query will only execute when <code>enabled</code> evaluates to <code>true</code>. Once <code>userId</code> gets a value. Tanstack query will fire automatically once the reactive property returns <code>true</code>. If this property reverts to <code>false</code> the query no longer refetch when stale.</p><h2 id="wrapping-up">Wrapping Up</h2><p>TanStack Query takes a lot of the pain out of data fetching in Vue applications. With minimal setup, we get automatic caching, configurable stale times, background refetching on window focus and reconnect, polling, and clean loading and error states.</p><p>We&rsquo;ve only scratched the surface here. TanStack Query also supports mutations for creating and updating data, optimistic updates, infinite queries for pagination and a fantastic set of <a target="_blank" href="https://tanstack.com/query/latest/docs/framework/vue/devtools">devtools</a> for debugging your queries in the browser.</p><p>I recommend taking a deep dive into the <a target="_blank" href="https://tanstack.com/query/latest/docs/framework/vue/overview">official documentation</a> as it is very well written and packed with examples.</p><p>Happy fetching!</p>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:95c1b59f-0f37-46a9-a08b-2560c179c4bf</id>
    <title type="text">Getting Contact Information with .NET MAUI</title>
    <summary type="text">Let users easily pull contact info (like name, number, email) from one app and save it to their device’s contact list in .NET MAUI.</summary>
    <published>2026-06-08T17:06:18Z</published>
    <updated>2026-06-18T18:00:59Z</updated>
    <author>
      <name>Leomaris Reyes </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/getting-contact-information-net-maui"/>
    <content type="text"><![CDATA[<p><span class="featured">Let users easily pull contact info (like name, number, email) from one app and save it to their device&rsquo;s contact list in .NET MAUI.</span></p><p>Let&rsquo;s be honest, the only &ldquo;accessory&rdquo; you take with you everywhere&mdash;no matter the activity you&rsquo;re doing or the type of event it is, and something that never bothers you to carry&mdash;is your mobile device. </p><p>OK &hellip; and what does this mean? It means that a large part of the things you need to do in your day-to-day life are covered by your phone: from super basic operations like calculating amounts and saving contacts, to talking with your friends or even ordering food.</p><p>Now, think about this: imagine you have an application with a form where you need to enter contact information&mdash;the same information you already saved on your phone when creating that contact. Having to type everything again can feel tedious, right?</p><p>This is where things get interesting. A simple action like saving a contact opens the door for multiple applications to obtain that information and work together with the data that already exists on the device. If your app could &ldquo;pull&rdquo; that information directly, without the user having to type it again, that would be a win! </p><p>So get excited, because in this article I&rsquo;ll show you how to obtain that contact information to use it in your .NET MAUI apps. </p><h2 id="what-do-i-need-before-i-start">What Do I Need Before I Start?</h2><p>Before we begin, it&rsquo;s important to configure a few platform-specific steps. Let&rsquo;s go through each one, step by step:</p><h3 id="android">Android</h3><p>To be able to read contacts, you must explicitly request the <code>READ_CONTACTS</code> permission. There are three different ways to add this permission on Android:</p><p><strong>Option 1: Add the permission directly in the AndroidManifest.xml</strong></p><p>Go to Platforms &rarr; Android, open the AndroidManifest.xml file, and add the following node:</p><pre class=" language-xml"><code class="prism  language-xml">&lt;uses-permission android:name="android.permission.READ_CONTACTS" /&gt;
</code></pre><p><strong>Option 2: Use the Android Manifest graphical editor</strong></p><p>Go to <strong>Platforms &rarr; Android,</strong> double-click the <strong>AndroidManifest.xml</strong> file, and locate the <strong>Required permissions</strong> section. Find the permission labeled <strong>READ_CONTACTS</strong> and simply check the option, as shown below.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-05/01_readcontacts.png?sfvrsn=6ec6edc6_2" title="Read Contacts Permissions" alt="" /></p><p><strong>Option 3: Add the assembly-based permission</strong></p><p>Go to <strong>Platforms &rarr; Android &rarr; MainApplication.cs</strong> and add the permission as follows:</p><pre class=" language-csharp"><code class="prism  language-csharp">[assembly: UsesPermission(Android.Manifest.Permission.ReadContacts)]
</code></pre><h3 id="iosmac-catalyst">iOS/Mac Catalyst</h3><p>On <strong>iOS</strong> and <strong>Mac Catalyst</strong>, we also need a very similar configuration to be able to access contacts. Follow these steps:</p><p><strong>Step 1:</strong><br />Right-click on <strong>Platforms &rarr; iOS &rarr; Info.plist</strong> and on <strong>Platforms &rarr; MacCatalyst &rarr; Info.plist</strong> (yes, each configuration is done per platform) and open the file with the editor.</p><p><strong>Step 2:</strong><br />Once the file is open, add the <strong>NSContactsUsageDescription</strong> key along with its description. This step is mandatory to comply with Apple&rsquo;s guidelines. Keep in mind that this text must be very precise, as it will be the message shown to the user when the app requests permission to access their contacts.</p><pre class=" language-xml"><code class="prism  language-xml">&lt;key&gt;NSContactsUsageDescription&lt;/key&gt;
&lt;string&gt;This app needs access to your contacts to select a contact and retrieve its information.&lt;/string&gt;
</code></pre><h3 id="windows">Windows</h3><p>Contact picking is not supported on Windows.</p><h2 id="let’s-start">Let&rsquo;s Start!</h2><p>All set! We&rsquo;ve prepared the initial configuration to get started! </p><p>.NET MAUI provides the IContacts interface, which allows us to select and retrieve information from the contacts stored on the device.</p><p>This interface is part of the Microsoft.Maui.ApplicationModel.Communication namespace and is available through the Default property, which gives us access to the ready-to-use implementation provided by .NET MAUI.</p><h3 id="important-considerations-for-ios-and-macos">Important Considerations for iOS and macOS</h3><p>There is a namespace conflict because multiple classes named Contacts exist. As a result, if you write only <code>Contacts</code>, .NET doesn&rsquo;t know exactly which one you are referring to, and you might think there is an issue with your code when in reality you are just using the wrong class.</p><p><strong>What&rsquo;s the Solution?</strong></p><p>To avoid this conflict, you must tell .NET exactly where the class you want to use comes from. This is done by using the fully qualified name:</p><pre class=" language-csharp"><code class="prism  language-csharp">Microsoft.Maui.ApplicationModel.Communication.Contacts
</code></pre><p><strong>A Cleaner Solution</strong></p><p>There is also a cleaner way to handle this. Instead of writing the full name every time you use it, you can create an alias with a using directive, like this:</p><pre class=" language-csharp"><code class="prism  language-csharp">using Communication = Microsoft.Maui.ApplicationModel.Communication;
</code></pre><p>This gives the namespace a shorter name. Then, you can use that alias directly in your code:</p><pre class=" language-csharp"><code class="prism  language-csharp">var contact = await Communication.Contacts.Default.PickContactAsync();
</code></pre><h2 id="what-contact-information-can-we-retrieve-exactly">What Contact Information Can We Retrieve Exactly?</h2><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-05/02_contact.png?sfvrsn=23e5e456_2" title="Contacts" alt="" /></p><p>Let&rsquo;s take a look at the data that can be retrieved from a contact. The following are the available fields:</p><ul><li><strong>Id:</strong> The internal identifier of the contact on the device.</li><li><strong>NamePrefix:</strong> The name prefix (Mr., Mrs., Eng., Dr., etc.)</li><li><strong>GivenName:</strong> The contact&rsquo;s first name.</li><li><strong>MiddleName:</strong> The contact&rsquo;s middle name.</li><li><strong>FamilyName:</strong> The contact&rsquo;s last name.</li><li><strong>NameSuffix:</strong> The name suffix (if any), for example, Jr.</li><li><strong>Phones:</strong> The list of phone numbers associated with the contact.</li><li><strong>Emails:</strong> The list of email addresses associated with the contact.</li><li><strong>DisplayName:</strong> The full name displayed on the phone. For example: Eng. Maris Lopez.</li></ul><h2 id="getting-your-device-contact-list">Getting Your Device Contact List</h2><p>To retrieve the full list of contacts stored on your device, you can use the <code>GetAllAsync()</code> method. This method returns a collection containing all available contacts.</p><p>Below is an example showing how to retrieve them:</p><pre class=" language-csharp"><code class="prism  language-csharp">public async IAsyncEnumerable&lt;string&gt; GetContactNames() 
{ 
    var contacts = await Contacts.Default.GetAllAsync(); 
// No contacts 
if (contacts == null) 
    yield break;
 
foreach (var contact in contacts) 
    yield return contact.DisplayName; 
}
</code></pre><h3 id="but-how-can-i-select-a-specific-contact-">But How Can I Select a Specific Contact? </h3><p>To ask the user to select a specific contact from their device, we use the <code>PickContactAsync()</code> method. This method opens the system&rsquo;s contact list, where the user can simply choose a contact.</p><blockquote><p>⚠️ If the user does not select any contact or cancels the action, the method returns null.</p></blockquote><p>In the following example, you can see how to retrieve the different pieces of information from the selected contact:</p><pre class=" language-csharp"><code class="prism  language-csharp">private async void SelectContactButton_Clicked(object sender, EventArgs e) 
{ 
    try 
    { 
    var contact = await Contacts.Default.PickContactAsync(); 
    
    if (contact == null) 
    return; 
    string id = contact.Id; 
    string namePrefix = contact.NamePrefix; 
    string givenName = contact.GivenName; 
    string middleName = contact.MiddleName; 
    string familyName = contact.FamilyName; 
    string nameSuffix = contact.NameSuffix; 
    string displayName = contact.DisplayName; 
    List&lt;ContactPhone&gt; phones = contact.Phones; 
    List&lt;ContactEmail&gt; emails = contact.Emails; 
    }
    
    catch (Exception ex) 
    { 
    // Most likely permission denied 
    } 
}
</code></pre><h2 id="✍️-platform-differences-you-should-know">✍️ Platform Differences You Should Know</h2><h3 id="android-1">Android</h3><ul><li><code>GetAllAsync</code> does not support the <code>cancellationToken</code> parameter.</li></ul><h3 id="ios--mac-catalyst">iOS / Mac Catalyst</h3><ul><li><code>GetAllAsync</code> does not support the <code>cancellationToken</code> parameter.</li><li>The <code>DisplayName</code> property is not supported natively. Instead, its value is constructed using <code>GivenName FamilyName</code>.</li></ul><h2 id="conclusion">Conclusion</h2><p>And that&rsquo;s it!  In this article, you learned how to work with device contacts in .NET MAUI, from selecting a specific contact to retrieving the full contact list. You also explored key platform differences and important considerations to keep in mind when targeting Windows, Android, iOS and Mac Catalyst.</p><p>With this knowledge, you can now integrate contact access into your apps in a clear and reliable way, improving the user experience while handling platform-specific behaviors correctly.</p><p>See you in the next article! &zwj;♀️✨</p><h3 id="reference">Reference</h3><p>Code samples and explanations were based on the official documentation.</p><ul><li><a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/maui/platform-integration/communication/contacts?view=net-maui-10.0&amp;tabs=macios">https://learn.microsoft.com/en-us/dotnet/maui/platform-integration/communication/contacts?view=net-maui-10.0&amp;tabs=macios</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">Share Functionality in Your .NET MAUI Apps</h4></div><div class="col-8"><p class="u-fs16 u-mb0"><a target="_blank" href="https://www.telerik.com https://www.telerik.com/blogs/share-functionality-net-maui-apps">Learn how to add share functionality to your .NET MAUI app</a> whether in iOS, Android or Windows.</p></div></div></aside>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:288b4cc6-8c92-4dec-a932-bed600474298</id>
    <title type="text">Angular 22: The Evolution of Modern Angular</title>
    <summary type="text">Signals, OnPush, declarative async resources: Angular 22 makes standard several features that help developers build better apps.</summary>
    <published>2026-06-05T21:01:27Z</published>
    <updated>2026-06-18T18:00:59Z</updated>
    <author>
      <name>Dany Paredes </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/angular-22-evolution-modern-angular"/>
    <content type="text"><![CDATA[<p><span class="featured">Signals, OnPush, declarative async resources: Angular 22 makes standard several features that help developers build better apps.</span></p><p>A few days ago, we checked out <a target="_blank" href="https://www.telerik.com/blogs/google-io-2026-developers-moving-ai-agentic-era">Google I/O 2026</a>, and today we need to talk about our favorite framework: <strong>Angular</strong>. Why? Because <strong>version 22</strong> is here!</p><p>Angular v22 is a major update focused on stability, performance and ergonomic APIs. It brings a more polished signal-driven platform and better data loading primitives that make modern apps feel faster and simpler to build.</p><p>Before we look at the most interesting changes, let&rsquo;s talk about the foundation. Angular v22 stabilizes the features we have been testing in the last few versions. If you want to build a high-performance app today, these are the new standards.</p><h2 id="stable-signal-forms-and-httpresource">Stable Signal Forms and httpResource</h2><p>Reactive Forms were great, but they often felt separate from the modern Signal-based reactivity. In v22, <strong>Signal Forms are officially stable</strong>.</p><p>What does this mean for us?</p><ul><li><strong>Better reactivity:</strong> No more unnecessary updates when a single field changes.</li><li><strong>Better type safety:</strong> We finally have a form system that works perfectly with TypeScript.</li></ul><p>Do you remember <code>HttpClient</code> and the manual <code>toSignal</code> conversions? They worked, but they added extra code. Now we have <code>httpResource</code>.</p><p>It is the new standard for fetching data. It treats your HTTP requests as signals, automatically handling the loading, error, and data states for you.</p><blockquote><p><strong>Learn more:</strong> Dive deeper into <a target="_blank" href="https://www.telerik.com/blogs/getting-started-httpresource-api-angular">Getting Started with httpResource API</a> and learn more about <a target="_blank" href="https://www.telerik.com/blogs/angular-signal-forms-vs-reactive-forms">Angular Signal Forms vs. Reactive Forms</a>.</p></blockquote><p>But what if your request depends on another signal? In v22, we have the new <code>chain()</code> method. It allows you to link resources together. If the first resource is loading, the second one waits automatically. This is a big win for complex data!</p><p><strong>Wait, what if you are already using RxJS?</strong> Don&rsquo;t worry, Angular has a solution for that too. Let&rsquo;s see how v22 handles the transition from Observables to Resources.</p><h2 id="rxresource-vs.-httpresource">RxResource vs. httpResource</h2><p>A common question is: &ldquo;Should I stop using RxJS?&rdquo; The answer is <strong>no</strong>. Angular v22 introduces <code>rxResource</code> as the perfect partner for your existing Observable streams.</p><ul><li><strong><code>httpResource</code></strong>: Use this for standard API calls. It is optimized for the Fetch API and handles JSON automatically.</li><li><strong><code>rxResource</code></strong>: Use this when your data source is an existing Observable. It allows you to use the power of RxJS operators inside the clean, Signal-based Resource API.</li></ul><p>Now that we have our data fetching ready, how do we make sure our users and agents can navigate efficiently? Let&rsquo;s talk about the new smart navigation features in the Router.</p><h2 id="smart-navigation-and-incremental-hydration">Smart Navigation and Incremental Hydration</h2><p>The Angular Router received some updates in v22 that solve common problems.</p><ul><li><strong>URL decoupling:</strong> You can now keep a clean URL in the address bar while the app shows a different state. With the new <code>browserUrl</code> input on <code>RouterLink</code>, you can show <code>/settings</code> while the router actually uses <code>/</code><code>account/advanced-preferences</code>.</li><li><strong>Parameter inheritance:</strong> Child routes now inherit parameters from their parents by default, and you don&rsquo;t need extra configuration to get an ID from a parent route.</li></ul><p>But a great UI and smart routing mean nothing if the page is slow to load. This brings us to the most impressive performance update in v22: <strong>incremental hydration</strong>.</p><p>Server-Side Rendering (SSR) is no longer a problem. In v22, <strong>incremental hydration is the default</strong>.</p><p>Angular now loads your application in small parts as they become visible. Combined with <strong>resource caching</strong>, the client can use the data fetched on the server without making a second API call. The user sees the data instantly, and the &ldquo;flicker&rdquo; of reloading data is gone.</p><blockquote><p><strong>Learn more:</strong> Explore <a target="_blank" href="https://www.telerik.com/blogs/new-angular-hydration">The New Angular Hydration</a> for in-depth details.</p></blockquote><p>But performance is also about how the browser handles changes. This is where the biggest change in Angular&rsquo;s history reaches its peak.</p><h2 id="onpush-by-default">OnPush by Default</h2><p>For years, <code>ChangeDetectionStrategy.Eager</code> (previously known as `Default`) was the standard for Angular&rsquo;s change detection. With v22, <code>ChangeDetectionStrategy.OnPush</code>is now the default for new components!</p><p>By using <code>ChangeDetectionStrategy.OnPush</code> as the default, we get:
</p><ul><li><strong>Better performance:</strong> The framework only checks what it needs to, exactly when it needs to, avoiding unnecessary checks.</li><li><strong>A step closer to Zoneless:</strong> This sets the stage for eventually dropping <code>zone.js</code> in the future.</li></ul><p>If you have an older app, don&rsquo;t worry! There is a migration tool that adds <code>ChangeDetectionStrategy.Eager</code> (the new name for the old behavior) to your existing components exactly as before.</p><p>Now, how do we make sure our fast code actually works? Let&rsquo;s talk about the new speed king of testing.</p><h2 id="vitest">Vitest</h2><p>Testing used to be the slow part of our work. Not anymore. Angular v22 officially replaces Karma/Jasmine with <strong>Vitest</strong> as the default test runner.</p><p>It is very fast and shares the same configuration as your build pipeline. Also, the new <code>migrate-karma-to-vitest</code> tool makes the change very easy, even for complex tests!</p><blockquote><p>Do you want to try <a target="_blank" href="https://www.telerik.com/blogs/unit-testing-angular-modern-testing-vitest">Vitest</a>?</p></blockquote><h2 id="angular-aria-accessibility-for-everyone">Angular Aria: Accessibility for Everyone</h2><p>Building custom components with correct ARIA roles and focus management can be difficult. In v22, <strong>Angular Aria is officially stable</strong>.</p><p>Think of it as <strong>&ldquo;headless accessibility.&rdquo;</strong> It provides the logic you need to build your own accessible components. It handles the complicated parts of ARIA attributes and keyboard interactions for you.</p><p>Why does this matter for AI? Because accessible apps are much easier for AI Agents to understand!</p><blockquote><p>Check this example about <a target="_blank" href="https://www.telerik.com/blogs/build-accessible-components-angular-aria">how to build accessible components with Angular Aria</a>.</p></blockquote><h2 id="async-dependency-injection">Async Dependency Injection</h2><p>The <code>injectAsync()</code> function allows us to lazy-load services only when they are actually needed, while still creating the instance with Angular dependency injection. This keeps your app small and fast.</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">CheckoutComponent</span> <span class="token punctuation">{</span>
  <span class="token comment">// We declare the lazy injection in the injection context (class field)</span>
  <span class="token keyword">private</span> paymentGatewayLoader <span class="token operator">=</span> <span class="token function">injectAsync</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> 
    <span class="token keyword">import</span><span class="token punctuation">(</span><span class="token string">'./payment-gateway.service'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span>m <span class="token operator">=&gt;</span> m<span class="token punctuation">.</span>PaymentGatewayService<span class="token punctuation">)</span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">async</span> <span class="token function">onCheckoutCompleted</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// We only load the heavy PaymentGatewayService when the user actually checks out!</span>
    <span class="token keyword">const</span> paymentGateway <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">paymentGatewayLoader</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">await</span> paymentGateway<span class="token punctuation">.</span><span class="token function">processTransaction</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><h2 id="building-a-dashboard-with-angular-22">Building a Dashboard with Angular 22</h2><p>Instead of just explaining features, let&rsquo;s build a practical dashboard example that uses the stable core of Angular v22: <code>@Service()</code>, <code>httpResource()</code> and the new template syntax.</p><blockquote><p><strong>Want to follow along?</strong> You can clone the complete project from GitHub: <a target="_blank" href="https://github.com/danywalls/angular-22-nba-finals">angular-22-nba-finals</a>. Just run <code>npm start</code> and you&rsquo;ll have the dashboard running locally.</p></blockquote><h3 id="the-data-service-with-service">The Data Service with @Service</h3><p>Now, let&rsquo;s create our service. We will use the new <code>@Service()</code> decorator (which replaces <code>@Injectable()</code>) and <code>httpResource</code> to fetch the games. Notice how <code>httpResource</code> handles loading and error states for you.</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token comment">// src/app/nba.service.ts</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> Service <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/core'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> httpResource <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/common/http'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">NbaGame</span> <span class="token punctuation">{</span>
  id<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
  gameNumber<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
  homeTeam<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  homeScore<span class="token punctuation">:</span> <span class="token keyword">number</span> <span class="token operator">|</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
  awayTeam<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  awayScore<span class="token punctuation">:</span> <span class="token keyword">number</span> <span class="token operator">|</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
  status<span class="token punctuation">:</span> <span class="token string">'Final'</span> <span class="token operator">|</span> <span class="token string">'Scheduled'</span><span class="token punctuation">;</span>
  date<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  location<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

@<span class="token function">Service</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">NbaService</span> <span class="token punctuation">{</span>
  <span class="token comment">// httpResource is the standard for fetching data</span>
  gamesResource <span class="token operator">=</span> httpResource<span class="token operator">&lt;</span>NbaGame<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token string">'/api/nba-finals'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><h3 id="the-ui-layer--error-states">The UI Layer &amp; Error States</h3><p>We display the data using the new <code>@for</code> block and keep the error handling inside the component template. This keeps the example focused on stable Angular v22 behavior.</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token comment">// src/app/app.ts</span>
@<span class="token function">Component</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  template<span class="token punctuation">:</span> <span class="token template-string"><span class="token string">`
    &lt;app-dashboard&gt;&lt;/app-dashboard&gt;
    &lt;router-outlet&gt;&lt;/router-outlet&gt;
  `</span></span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
</code></pre><p>Our Dashboard component remains simple:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token comment">// src/app/dashboard.component.ts</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> Component<span class="token punctuation">,</span> inject <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@angular/core'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> NbaService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./nba.service'</span><span class="token punctuation">;</span>

@<span class="token function">Component</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  selector<span class="token punctuation">:</span> <span class="token string">'app-dashboard'</span><span class="token punctuation">,</span>
  template<span class="token punctuation">:</span> <span class="token template-string"><span class="token string">`
    &lt;div class="dashboard"&gt;
      &lt;h1&gt;NBA Finals 2026: Knicks vs Spurs&lt;/h1&gt;

      @if (nbaService.gamesResource.isLoading()) {
         &lt;p&gt;Loading game data...&lt;/p&gt;
      } @else {
        &lt;div class="cards"&gt;
          @for (game of nbaService.gamesResource.value(); track game.id) {
            &lt;div class="card"&gt;
              &lt;div class="meta"&gt;Game {{ game.gameNumber }} &bull; {{ game.date }}&lt;/div&gt;
              &lt;div class="score"&gt;
                {{ game.homeTeam }} {{ game.homeScore }} vs {{ game.awayScore }} {{ game.awayTeam }}
              &lt;/div&gt;
              &lt;div class="status"&gt;{{ game.status }}&lt;/div&gt;
            &lt;/div&gt;
          }
        &lt;/div&gt;
      }
    &lt;/div&gt;
  `</span></span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">DashboardComponent</span> <span class="token punctuation">{</span>
  nbaService <span class="token operator">=</span> <span class="token function">inject</span><span class="token punctuation">(</span>NbaService<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>Save changes and run <code>ng server</code> navigate in the browser and tada!!!</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/angular-22-signals-dashboard.png?sfvrsn=5aeda1c4_2" alt="Angular 22 example dashboard using signals and httpResource" /></p><p>Done!! We play with the stable part of Angular v22 using <code>@Service()</code> for a clean singleton service definition, <code>httpResource()</code> for declarative, signal-based HTTP loading and <code>@for</code> with <code>@if</code> in templates to render loading and data states.</p><p>And, of course, AI is not forgotten by the Angular team. They updated and added new tools for Angular MCP and Skills. Let&rsquo;s see them!</p><h2 id="angular-mcp-and-skills">Angular MCP and Skills</h2><p>Of course, Angular continues embracing AI. The Angular MCP exposes app state and builds workflows for tools that understand Angular projects. That includes server-side MCP tools such as <code>devserver.wait_for_build</code>, <code>devserver.start</code> and <code>devserver.stop</code>.</p><p>Version 22 also introduces Agent Skills for Angular, a lightweight set of skill definitions that give AI assistants direct knowledge of modern Angular idioms and best practices. These skills are useful for teams that want to integrate agent-assisted coding into their workflow without pain.</p><ul><li>Learn more about Angular MCP: <a target="_blank" href="https://angular.dev/ai/mcp">angular.dev/ai/mcp</a></li><li>Learn more about Agent Skills for Angular: <a target="_blank" href="https://github.com/angular/skills">github.com/angular/skills</a></li></ul><h2 id="recap">Recap</h2><p>Angular 22 is a declaration that the way we build for the web has evolved. And by using <strong>Signals</strong>, <strong>OnPush performance</strong> and <strong>declarative async resources</strong>, we can build applications that are faster, simpler and easier to maintain.</p><p>This release is about making stable patterns:</p><ul><li>Use <code>httpResource()</code> for fetch-heavy components.</li><li>Use <code>@Service()</code> for singleton services.</li><li>Use <code>injectAsync()</code> when you want to defer heavy service loading.</li><li>Let template syntax like <code>@for</code> and <code>@if</code> keep your view code readable.</li><li>Embrace the AI era with Agent Skills and MCP tools.</li></ul><p>My advice is don&rsquo;t just read this article&mdash;build something! Take the dashboard we sketched and turn it into an interactive experience with Progress <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/ai-chat">Kendo UI for Angular AI Chat</a>. A chat interface is a powerful way to explore the series score in real time and it&rsquo;s a great way to test Angular 22 with a modern Kendo UI scenario.</p><p>Feel free to <a target="_blank" href="https://youtu.be/h5OJUSS_8IA?is=WBc_bZuuQnx9b0EY">watch for all the new features in Angular 22</a>:</p><div data-sf-ec-immutable="" class="-sf-relative" contenteditable="false" style="width:560px;height:315px;"><div data-sf-disable-link-event=""><iframe width="560" height="315" src="https://www.youtube.com/embed/h5OJUSS_8IA?si=0Nzrxxm5BbAjRJz6" title="What’s new in Angular v22" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"></iframe></div></div><p>&nbsp;</p><p>For a complete overview and to verify all the new features, you can check the official release post: <a href="https://blog.angular.dev/announcing-angular-v22-c52bb83a4664" target="_blank">Announcing Angular v22</a>.</p><p><strong>Happy coding!</strong></p>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:f5808969-92f6-4e38-b801-f36a47627abf</id>
    <title type="text">Visual Studio 2026, 6 Months Later</title>
    <summary type="text">Throughout this post, we’ll discuss Visual Studio updates and improvements, such as performance, GitHub Copilot, Hot Reload and more.</summary>
    <published>2026-06-04T21:42:24Z</published>
    <updated>2026-06-18T18:00:59Z</updated>
    <author>
      <name>Dave Brock </name>
    </author>
    <link rel="alternate" href="https://www.telerik.com/blogs/visual-studio-2026-6-months-later"/>
    <content type="text"><![CDATA[<p><span class="featured">Throughout this post, we&rsquo;ll discuss Visual Studio updates and improvements, such as performance, GitHub Copilot, Hot Reload and more.</span></p><p>I remember exactly where I was when <a target="_blank" href="https://devblogs.microsoft.com/visualstudio/visual-studio-2026-is-here-faster-smarter-and-a-hit-with-early-adopters/">Visual Studio 2026 dropped</a> back in November: at my desk, watching the Microsoft launch event with one eye and refactoring an <code>IDataReader</code> implementation with the other.</p><p>The keynote was typical Microsoft. There was a lot of &ldquo;reimagining how developers work.&rdquo; There was, of course, a slide with the word &ldquo;intelligent&rdquo; on it at least eight times. And the word &ldquo;Copilot&rdquo; appeared on screen so many times I started to wonder if Microsoft&rsquo;s marketing team had been replaced by an infinite loop that just prints <code>Copilot</code> forever.</p><p>Then I installed the thing.</p><p>The first week with VS 2026 was the most disoriented I&rsquo;ve felt in a Visual Studio release since <a target="_blank" href="https://devblogs.microsoft.com/visualstudio/a-design-with-all-caps/">the menu bar went all-caps in 2012</a> and I spent a month reading angry forum threads about it.</p><p>In 2026, I was tripped up by the just-slightly different Solution Explorer, the Copilot panel that lives where my Test Explorer used to live, the general Copilot onslaught and the interesting design choices. I felt like everything on my desk had been moved slightly to the left.</p><p>That was temporary. As with most new technology, eventually things felt normal again. I was finding workflows that genuinely beat my old ones and I stopped noticing the cosmetic changes. So here&rsquo;s where I am six months later &hellip; same machine, same kind of solution I work in every day, just a different IDE around it.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/vs2026-overview.png?sfvrsn=4ef376fb_2" alt="" /><br /><span style="font-size:14px;">We meet again, old friend. At least the menu bar isn't in caps.</span></p><p>Six months in, I feel comfortable. Six months is long enough to live with the rough edges, and find what&rsquo;s genuinely useful. So I&rsquo;m here to share them with you.</p><p>As an aside: to keep my sanity intact, I&rsquo;m going to use the word &ldquo;Copilot&rdquo; only when I absolutely have to. Microsoft has decided that every feature in Visual Studio is now Copilot-something &hellip; Copilot Chat, Copilot Edits, Copilot Agent, Copilot Workspaces, GitHub Copilot, Copilot for Azure, Copilot for Pull Requests and so on. So when I say &ldquo;the agent&rdquo; or &ldquo;the AI thing in the sidebar,&rdquo; you&rsquo;ll know what I mean.</p><h2 id="the-performance-story-is-real">The Performance Story Is Real</h2><p>I&rsquo;ll lead with this, mostly because it was the thing I was most skeptical about. Every Visual Studio release for the past decade has touted &ldquo;X% faster solution load!&rdquo; and, to me, every Visual Studio release for the last decade has felt (roughly) the same speed once you test it with your beefy codebase.</p><p>VS 2026 is genuinely faster. Not &ldquo;marketing-faster&rdquo; &hellip; actually faster. The <a target="_blank" href="https://devblogs.microsoft.com/visualstudio/visual-studio-2022/">64-bit-everywhere story that started with VS 2022</a> has matured into something that handles large solutions without the periodic &ldquo;please hold&rdquo; pauses we&rsquo;re all familiar with. In Microsoft&rsquo;s VS2026 launch post, they say they <a target="_blank" href="https://devblogs.microsoft.com/visualstudio/visual-studio-2026-is-here-faster-smarter-and-a-hit-with-early-adopters/">cut hangs by over 50%</a>.</p><p>The IntelliSense responsiveness is the part I notice most. Typing a <code>.</code> after an object now produces suggestions roughly when I expect them to appear. A low bar? Definitely, but previous versions frequently limboed under it.</p><h2 id="agent-mode-is-useful-when-you-pick-the-right-job">Agent Mode Is Useful, When You Pick the Right Job</h2><p>GitHub Copilot&rsquo;s <a target="_blank" href="https://learn.microsoft.com/en-us/visualstudio/ide/copilot-agent-mode?view=visualstudio">agent mode in Visual Studio 2026</a>&mdash;or, as Microsoft prefers, <em>GitHub Copilot Agent Mode powered by Copilot in Visual Studio with Copilot</em>&mdash;has improved tremendously since Visual Studio 2022. Six months in, I think it&rsquo;s genuinely useful, but for a specific set of tasks.</p><p>In my experience, it works well with scoped, well-defined refactors. If I want to ask: <em>Hey Copilot, add a new health check for this Cosmos container and follow the pattern in my existing health checks.</em> (Yes, I sometimes talk to Copilot like a home assistant.) Or: <em>Take my new controller and add the same logging pattern as the others in this project.</em> And, after discovering some methods in a codebase with 11 nested if statements: <em>Convert this <code>for</code> loop to a LINQ expression but keep it readable.</em> It&rsquo;s effective enough that I&rsquo;ve stopped writing this kind of code by hand.</p><p>In my experience, things get harder when the agent needs to understand why something exists. The agent will happily refactor a piece of code that only exists because of, say, a weird third-party integration constraint from 2018, and the resulting code will be cleaner, more maintainable &hellip; and broken in production. Did I actually break something in production? Not this time. But I would have.</p><p>To be fair to the agent, this is exactly where it needs context. It doesn&rsquo;t read minds (although it sure does try). Here&rsquo;s a pattern I&rsquo;ve leaned on.</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token comment">// AGENT NOTES:</span>
<span class="token comment">// - This service must remain synchronous; the upstream caller cannot</span>
<span class="token comment">//   be made async without a contract change.</span>
<span class="token comment">// - The string "PRIME" is required by the third-party integration. </span>
<span class="token comment">//   I don't like it but they won't change it. Do not "improve" it </span>
<span class="token comment">//   to PersonType.Prime.ToString().</span>
<span class="token comment">// - Cancellation tokens must be forwarded; do NOT pass `default`.</span>
</code></pre><p>When the agent has clear context like this, it really delivers. Below is the agent producing two new health check files plus a registration update in <code>Program.cs</code>, all from one prompt asking it to mirror an existing pattern. The result is a careful refactoring with the right substitutions swapped in. That&rsquo;s exactly the kind of work I&rsquo;m happy to hand off.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/gh-copilot-agent.png?sfvrsn=a2b5eefe_2" alt="" /><br /><span style="font-size:14px;">The agent panel and one of the two generated files. Three files changed, zero hand-written by me. (My team is thankful for the latter.)</span></p><p>This sounds like babysitting, and it is. Welcome to AI-driven development, my friends. The 10 seconds of setup saves me from a 10-minute cleanup later. When the prep is solid, the result is solid.</p><p>For more information on <a target="_blank" href="https://learn.microsoft.com/en-us/visualstudio/ide/copilot-chat-context?view=visualstudio#use-custom-instructions">custom instructions, check out the Microsoft documentation</a>.</p><h2 id="test-explorer-isnt-frustrating">Test Explorer Isn&rsquo;t Frustrating</h2><p>In my experience, the Test Explorer seems to be much improved in Visual Studio 2026. If I had to sum it up in previous versions, I&rsquo;d say &ldquo;it&rsquo;s fine&rdquo; &hellip; after all, do we ever get jazzed about tests? In previous releases, I kept saying &ldquo;it&rsquo;s fine&rdquo; until I had a few thousand tests, at which point it started getting opinionated about which tests to run.</p><p>The new version is faster, the filtering predictably works, and it doesn&rsquo;t lose track of which tests are running when you switch branches mid-execution. Try it yourself. Run your full test suite and watch the Test Explorer not embarrass itself. It&rsquo;s a small joy.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/test-explorer.png?sfvrsn=222a8ba_2" alt="" /><br /><span style="font-size:14px;">One of those things you don't notice until it stops getting in your way.</span></p><h2 id="better-git-integration">Better Git Integration</h2><p>Visual Studio&rsquo;s Git tooling has historically been one of the IDE&rsquo;s more underwhelming features. Until recently, my workflow was:</p><ol><li>Make code changes in Visual Studio.</li><li>Run Git commands in my terminal (or a third-party tool like GitHub Desktop)</li><li>Ignore the IDE&rsquo;s Git panel entirely.</li></ol><p>With VS 2026, I&rsquo;m finally doing real Git work inside the tool. The new staging interface is better. The conflict resolution UI is also better than it used to be. The &ldquo;please show me an actual diff and not a vague summary&rdquo; feature is good.</p><p>Now, it&rsquo;s no GitKraken and it doesn&rsquo;t try to be. But it&rsquo;s good enough that I no longer switch over to a terminal for routine operations, and that works for me.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/git-conflict.png?sfvrsn=62f8884a_2" alt="" /><br /><span style="font-size:14px;">Three-way merge view, in the IDE I'm already in. Rejoice!</span></p><h2 id="hot-reload-that-reloads-more-often">Hot Reload That Reloads More Often</h2><p>Hot Reload has been one of those features that worked on Friday afternoons if I said &ldquo;please work&rdquo; (especially for ASP.NET Core). Any other time, I&rsquo;d change a method body, save, watch the IDE think very hard about it, and then get: <em>&ldquo;Hot Reload was unable to apply your changes. Restart the application?&rdquo;</em></p><p>The &ldquo;hot&rdquo; in &ldquo;Hot Reload&rdquo; was <a target="_blank" href="https://www.reddit.com/r/dotnet/comments/1my662s/will_microsoft_ever_fix_hot_reload_in_net/">stretching the truth</a> at times (with the understanding that getting it right <a target="_blank" href="https://www.reddit.com/r/dotnet/comments/1my662s/comment/naac88s/?utm_source=share&amp;utm_medium=web3x&amp;utm_name=web3xcss&amp;utm_term=1&amp;utm_content=share_button">isn&rsquo;t as simple in .NET</a> as it is for languages like JavaScript).</p><p>With VS 2026, I now reach for it instinctively instead of cynically. Method-body edits in controllers, services and Razor pages just apply. You change a string in a controller, hit Save and see the change. You know, the way it was supposed to work in 2021. The way it kind of worked in 2023. The way it now actually works in 2026.</p><p>When Hot Reload can&rsquo;t apply a change (because the change requires a metadata update or you&rsquo;ve added a field or whatever the actual reason is), the IDE now tells me clearly why instead of vaguely throwing up its hands. That alone goes a long way. I know whether to keep iterating or to just restart the host.</p><h3 id="debugger-improvements-especially-with-async-traces">Debugger Improvements (Especially with Async Traces)</h3><p>Have you ever stared at an async stack trace and felt your soul leave your body? Just me?</p><p>Historically, when an exception bubbled up from somewhere deep in an <code>async</code>/<code>await</code> chain, the stack trace was a wall of <code>MoveNext</code> calls and state machine internals. It was tremendously useful if you wanted to learn about the C# compiler, but not great if you wanted to know which of your methods actually threw.</p><p>Things are looking better in Visual Studio 2026. The debugger now stitches together async call chains into something that reads like the call graph you actually wrote. The <code>MoveNext</code> noise is collapsed by default. The original calling method is shown as the parent frame and not buried six levels down. When an exception is thrown inside a <code>Task.WhenAll</code>, you can see which task threw without reverse-engineering the parallel structure.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-06/debugger.png?sfvrsn=53980e30_2" alt="" /><br /><span style="font-size:14px;">An async exception, with the actual call chain visible. No <code>MoveNext</code> translation required.</span></p><p>On top of that, conditional breakpoints feel faster to evaluate, and the tracepoints (where you can log a message without breaking) is a one-click affair instead of a buried right-click option.</p><p>Small wins. But debugging is the thing I do every day, and small wins on the thing you do every day add up fast.</p><h2 id="the-verdict">The Verdict</h2><p>Look, the Copilot stuff is genuinely impressive. The agent demos at the keynote were the kind of thing that makes you want to fire up the installer immediately. And after six months, I&rsquo;ll admit that I use it every day.</p><p>But Visual Studio 2026 is a genuine step forward for the boring reasons. The performance is real. The Test Explorer doesn&rsquo;t stress me out. The debugger provides stack traces a human can read. The agent mode handles the kind of work I&rsquo;d previously do by hand. Those are the wins that show up in my daily work, not the ones that show up in keynote demos.</p><p>If you&rsquo;re on Visual Studio 2022 and wondering whether to upgrade: yes. Ignore the marketing, and let the unglamorous improvements do their job. Six months later, you won&rsquo;t be excited about Visual Studio 2026 &hellip; and that&rsquo;s exactly the point. You&rsquo;ll just be working faster.</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">Extension Properties: C# 14&rsquo;s Game-Changing Feature for Cleaner Code</h4></div><div class="col-8"><p class="u-fs16 u-mb0">This post introduces <a target="_blank" href="https://www.telerik.com/blogs/extension-properties-csharp-14-game-changing-feature-cleaner-code">extension properties</a>, a new feature of C# 14 that allows you to use properties in your extension methods.</p></div></div></aside>]]></content>
  </entry>
</feed>
