<?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>
  <link rel="hub" href="https://feedpress.superfeedr.com/"/>
  <logo>https://static.feedpress.com/logo/telerik-blogs-productivity-6185247624f05.jpg</logo>
  <title type="text">Telerik Blogs | Productivity</title>
  <subtitle type="text">The official blog of Progress Telerik - expert articles and tutorials for developers.</subtitle>
  <id>uuid:c9587aa7-6488-4999-ab92-90eeabc12f99;id=2493</id>
  <updated>2026-10-22T21:58:28Z</updated>
  <link rel="alternate" href="https://www.telerik.com/"/>
  <link rel="self" type="application/atom+xml" href="https://feeds.telerik.com/blogs/productivity"/>
  <entry>
    <id>urn:uuid:e22da2d4-9ee4-48a9-894b-7181ae607c97</id>
    <title type="text">I Did a Thing: The Friction of Letting an Agentic Workflow Do the Work</title>
    <summary type="text">Building with agents feels like farming badly. The agentic workflow isn’t broken. I’m leading something I don’t understand yet, and that’s a leadership problem.</summary>
    <published>2026-10-20T15:41:19Z</published>
    <updated>2026-10-22T21:58:28Z</updated>
    <author>
      <name>Teon Beijl </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23068/17501452/i-did-thing-friction-letting-agentic-workflow-work"/>
    <content type="text"><![CDATA[<p><span class="featured">Building with agents feels like farming badly. The agentic workflow isn&rsquo;t broken. I&rsquo;m leading something I don&rsquo;t understand yet, and that&rsquo;s a leadership problem.</span></p><p>If you watch <em>Clarkson&rsquo;s Farm</em>, you&rsquo;ve heard the catchphrase <strong>&ldquo;I did a thing!&rdquo;</strong> when Jeremy has performed a miracle by successfully executing a relatively basic farmer&rsquo;s task. That&rsquo;s how I feel right now.</p><p>I&rsquo;m building my own app using AI and I feel pretty lost and under-equipped to do this properly. Like in Clarkson&rsquo;s Farm, I know that if I handed the same work to someone like Kaleb, who actually knows what he&rsquo;s doing, it would be in a better state.</p><p>I&rsquo;m at a crossroads. On one hand, I want to go back to the way I&rsquo;m familiar with. On the other, I know I need to continue transforming the way I work.</p><h2 id="the-pull-to-go-back">The Pull to Go Back</h2><p>I want to go back to drawing. Back to sketching things out myself, by hand, where I have control over the outcome and the output with my own skill set.</p><p>It feels easier. <em>Give it to me, I&rsquo;ll do it.</em></p><p>But I want to be careful here, because this is the part where a lot of people stop and conclude that the agentic workflow doesn&rsquo;t work. I don&rsquo;t believe that. If I said, &ldquo;I can&rsquo;t make decisions, so I&rsquo;m going back to the way it was,&rdquo; I&rsquo;d be dismissing the whole value proposition, and that would be wrong.</p><p>The problem isn&rsquo;t that the agentic workflow is broken. The problem is that I lack the experience and the understanding to build products this way. I can choose to retreat or to redesign the way I work.</p><p>The value I bring needs a new way to be released.</p><h2 id="i-have-been-in-this-transition-before">I Have Been in This Transition Before</h2><p>What makes this recognizable to me is that I&rsquo;ve experienced this role shift before.</p><p>When I moved from being an individual contributor to being a design leader, it was a similar shift. Not the same shift, but a similar one. I had full control over outcomes with my own hands. Then I needed to let that go.</p><p>I remember the exact pull I feel now. <em>Just give it to me and let me do it.</em></p><p>What I had to do instead was unlearn. And in place of that, I had to reinvent myself. I had to train and educate myself into being a better leader, a servant leader. I had to articulate and transfer my know-how to the people I was now handing my work over to. And I had to put systems in place to set them up for success, so that the whole way of working improved.</p><p>That&rsquo;s exactly what I&rsquo;m facing right now, except the people are agents.</p><h2 id="the-part-im-actually-losing">The Part I&rsquo;m Actually Losing</h2><p>The biggest lesson I took from moving into agile development was three words: transparency, inspection and adaptation. The empirical pillars. You make the work visible, you look at it thoroughly and you change course based on what you saw. It&rsquo;s how you lead something you&rsquo;re not personally executing.</p><p>All three are breaking for me.</p><p>How the agents got to the decisions they made is pretty hard to see. You throw an assignment into a black box and something comes back. You lose control of the overview. You lose the continuity. You lose the clarity about how we&rsquo;re building this and why we&rsquo;re building this.</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">The call for observability is growing louder</h4></div><div class="col-8"><p class="u-fs16 u-mb0"><a target="_blank" href="https://www.telerik.com/blogs/six-questions-every-ai-trace-should-answer">An AI trace should explain what the agent decided</a>, what data it used, what it cost or whether the output was any good. Progress Agent Engineering does this for teams building in Python, .NET and JavaScript/TypeScript.</p></div></div><hr class="u-mb3" /></aside><p>Because I don&rsquo;t understand a lot of the code, I hand off work to something I can&rsquo;t properly judge. It does take away the risk of micromanagement, which is a positive side-effect, but it also means I have very little to inspect.</p><p>What surprised me is that it happens in my design too. Design is my own domain. I know this work. And still I find myself over-explaining, and still I can&rsquo;t rely on the same level of output. I haven&rsquo;t properly instructed the agents with the skills, prompts and libraries to really work and think like me.</p><p>This results in constant adaptation and reattempts, but without transparency and inspection. And without those two, adaptation isn&rsquo;t really adaptation. I&rsquo;m not changing course based on what came back. I&rsquo;m just asking for it again.</p><p>I&rsquo;m losing agility.</p><h2 id="left-or-right">Left or Right</h2><p>Being agile isn&rsquo;t about being fast. With agentic workflows your time is compressed, your own thinking gets rushed. You&rsquo;re constantly facing decisions and outputs from agents that come back so fast. The time to think in between is just gone.</p><p>And it feels like I&rsquo;m moving fast. But when I look at my actual progress, I find myself looping and losing control. Agility is about delivering value sustainably. Definitely not about burning tokens rapidly.</p><p>The hardest part is that I know I&rsquo;d actually be faster if I stepped away from the generation for a moment. Sketch things out. Give a proper drawing as input. Create a better frame for the agents to operate in.</p><p>But that&rsquo;s in direct conflict with the time I have available.</p><p>There&rsquo;s this constant intersection. I can go right and generate more, or I can go left, stop doing and think a little. My brain says go left. And then right there is the dopamine shot, the next generation, something new, something potentially better, maybe this one is the fix.</p><p>Going right feels like it takes me exactly where I want to be. It usually doesn&rsquo;t.</p><p>It&rsquo;s almost like a casino. I&rsquo;m constantly choosing red or black. I&rsquo;m gambling my time and my tokens with a lot of hoping.</p><h2 id="what-this-is-really-about">What This Is Really About</h2><p>I&rsquo;ve generated a lot of iterations. I keep looping. And underneath all of it is something simpler than a tooling problem.</p><p>You can&rsquo;t lead, challenge or guide people and agents in the right direction if the direction itself is ambiguous.</p><p>That&rsquo;s what I&rsquo;m struggling with. I know that I&rsquo;m responsible and need to guide something I don&rsquo;t comprehend enough myself. So the question becomes: do you double-down on the workforce you&rsquo;ve paid for, in this case agents, and just trust them and give them more? Or do you stop and build your own clarity first?</p><p>I&rsquo;m not going to hand you a technical answer here, because I&rsquo;m too early in this process to advise on that. Many of you reading this are further along than I am. Some are not.</p><p>But I do know the leadership version of this answer, because I&rsquo;ve had to learn it before.</p><p>As a leader you have to develop the skill and the character to accept that you don&rsquo;t know. And then to tell the people you need some time to formulate your thoughts, your vision, your direction, so you can properly guide them and send them the right way. If you don&rsquo;t develop that part, and if you&rsquo;re not creating room for it, you end up directing people in directions without the honest answer about what it should be.</p><p>That applies to agents too. Maybe even more.</p><h2 id="closure">Closure</h2><p>Maybe the thing to do isn&rsquo;t to take advice from me. Maybe it&rsquo;s just to reassess your own point of view.</p><p>Are you gambling, or are you creating clarity?</p><p>Take a break. Create the room to think, to not know, to doubt, to figure things out. And then treat developing your leadership skills as part of your agentic experience, not as something separate from it.</p><p>Create that clarity, build the systems and make room for the thinking that lets you guide both people and agents. Guide development in a direction you can actually see, inspect and adapt.</p><p>I&rsquo;m still going to be the guy on the tractor shouting that &ldquo;I did a thing.&rdquo; But there&rsquo;s more work to be done to make this field yield.</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">IA Prompting: Reframe and Relate</h4></div><div class="col-8"><p class="u-fs16 u-mb0">Good AI needs good IA. Information architecture is the old school skill designers and developers need to reclaim. Start with two things: <a target="_blank" href="https://www.telerik.com/blogs/ia-prompting-reframe-relate">reframe and relate</a>.</p></div></div></aside><img src="https://feeds.telerik.com/link/23068/17501452.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:5890abe0-8c6a-4973-85a3-623de3e0b32f</id>
    <title type="text">Telerik Support by the Numbers: Response Times, Resolution Rates and Technical Expertise</title>
    <summary type="text">When a product or tool is a crucial part of your application, support is more than just a nice-to-have. Here’s how Progress Telerik support stacks up.</summary>
    <published>2026-09-09T14:31:35Z</published>
    <updated>2026-10-22T21:58:28Z</updated>
    <author>
      <name>Kathryn Grayson Nanz </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23068/17443642/telerik-support-response-times-resolution-rates-technical-expertise"/>
    <content type="text"><![CDATA[<p><span class="featured">When a product or tool is a crucial part of your application, support is more than just a nice-to-have. Here&rsquo;s how Progress Telerik support stacks up.</span></p><p>When a product or tool is a crucial part of your application, support is more than just a nice-to-have. For enterprise development teams, support issues could mean anything from a single blocked developer to an entire delayed release. The larger and more critical the application, the more important predictable access to technical expertise becomes.</p><p>So rather than simply saying Progress provides strong Telerik and Kendo UI support, here are the numbers behind it&mdash;customer satisfaction, response and resolution times, escalation rates and the technical resources available beyond a simple or individual support case.</p><h3>Support at a Glance</h3><ul><li><strong>98%+</strong> customer satisfaction on case closure</li><li><strong>95.5%+</strong> customer satisfaction per interaction</li><li><strong>24-hour</strong> response-time Service Level Agreement (SLA)</li><li><strong>50%+</strong> of cases resolved within 14 hours</li><li><strong>&lt;14 hours</strong> median time to first response</li><li><strong>~99.8%</strong> of tickets resolved without escalation</li><li><strong>New AI-powered chat </strong>embedded in ticket submission and documentation to provide instant answers</li></ul><h2>What Does Enterprise-Grade Support Mean?</h2><p>Enterprise-grade technical support combines predictable response commitments, access to technical expertise, efficient issue resolution and self-service resources that help teams resolve problems without creating prolonged development blockers.</p><h3>Technical Support from Technical Engineers</h3><p>We believe that good support is part of the product experience. Our technical support engineers bring hands-on expertise, helping teams get from problem to solution without unnecessary delays or layers of escalation. Telerik Technical Support Engineers are technical specialists with software development expertise, equipped to investigate implementation-level issues rather than simply triage and route them. Approximately 99.8% of support tickets are resolved within Technical Support without requiring escalation to product engineering.</p><h3>Fast Turnaround</h3><p>We also understand that when a project is blocked, timing is of the essence. That&rsquo;s why our support teams provide a <strong>24-hour response-time SLA</strong>&mdash;but most of the time, you won&rsquo;t even have to wait that long. Our <strong>median time to first response is under 14 hours</strong>. </p><p>In fact, over half of support cases are <em>resolved </em>within 14 hours, helping teams move from problem to solution quickly. Most issues also stay with the support organization rather than requiring engineering escalation. And by &ldquo;most,&rdquo; I mean &ldquo;very nearly all&rdquo;: approximately 99.8% of tickets are resolved without escalation.</p><h3>Exceptional Customer Satisfaction</h3><p>More than 98% of customers report satisfaction at case closure, and a 95.5%+ CSAT score (Customer Satisfaction) per interaction reflects high quality throughout the support experience.</p><h2>What About Support Beyond Tickets?</h2><p>Support is about more than just answering one-off tickets, though. That&rsquo;s why we offer a massive collection of technical knowledge that helps developers across the community&mdash;whether they&rsquo;re troubleshooting a problem, learning a feature, evaluating an API or looking for implementation examples. Those resources mean that developers may not even need to open a support ticket in the first place! The vast library of public technical knowledge empowers developers with the knowledge they need, saving direct technical support for unique and specific edge case instances.</p><h3>Available Public Technical Knowledge</h3><ul><li><strong>290K+ </strong>answered forum threads</li><li><strong>6.5K+</strong> knowledge base articles</li><li><strong>10K+ </strong>demos</li><li><strong>24K+ </strong>conceptual documentation articles</li><li><strong>29K+</strong> API reference articles.</li><li><strong>100+ </strong>answered community questions each month</li></ul><p>These resources are publicly searchable and indexable, which allows the impact of our excellent support to scale far beyond what could be offered via tickets alone. Developers can benefit from solutions documented through years of customer and community interactions, while organizations can build their own expertise on top of a large, accessible body of technical knowledge.</p><h2>What Our Customers Say</h2><p>The numbers are impressive, but what matters more to us is what our customers have to say about their support experiences. These are just a small sample of quotes from users who have worked with our support teams and had wonderfully positive experiences.</p><h3>Clear Instructions</h3><p><em>&ldquo;I&rsquo;m incredibly grateful for the technical assistance I received. The support was not only timely but also highly effective&mdash;resolving my issue quickly and professionally. The guidance was clear and spot-on, helping me identify the missing step and implement the correct solution without delay. Thanks to this help, everything is now working perfectly. I truly appreciate the responsiveness and expertise. This kind of support makes a huge difference!&rdquo;</em></p><h3>High-Quality Responses</h3><p><em>&ldquo;Your developer support is AWESOME! I can&rsquo;t imagine moving to another control suite. You guys are the best!&rdquo;</em> </p><h3>Fast and Detailed</h3><p><em>&ldquo;Prompt and on-point with all of our communications. I appreciate the detailed responses and explanations!&rdquo;</em></p><h3>Going the Extra Mile</h3><p><em>&ldquo;Spent extra time making sure I had the resources and information to fix my problem [&hellip;] created an example project and code snippets. Thank you so much!</em></p><h2>Better Support Means Better End Products</h2><p>Faster access to expert assistance can reduce delivery risk by helping developers resolve blockers before they become prolonged delays. Strong technical support and a comprehensive knowledge base also reduces engineering time spent troubleshooting, searching for answers, going in circles with AI chatbots or developing brittle workarounds&mdash;lowering the total cost of ownership over time. Support is, therefore, not simply a service attached to the product; it's part of the risk assessment that teams should evaluate when choosing a partner they expect to rely on for years.</p><p>Enterprise teams working with Telerik and Kendo UI can have confidence that when a problem arises, there&rsquo;s a reliable solution&mdash;and an experienced team behind the technology, just waiting to help.</p><h2>FAQ</h2><h3>How Quickly Does Telerik Technical Support Respond?</h3><p>Telerik Technical Support includes a 24-hour response-time Service Level Agreement (SLA), but our median time to first response is less than 14 hours.</p><h3>How Quickly Are Telerik Support Cases Resolved?</h3><p>Over half of cases are resolved within 14 hours, with less than 1% requiring escalation.</p><h3>Are Telerik Support Cases Handled by Technical Experts?</h3><p>Yes, Telerik Technical Support Engineers are technical specialists with software development expertise, equipped to investigate implementation-level issues rather than simply triage and route them.</p><h3>What Self-Service Support Resources Does Telerik Provide?</h3><p>Our library of self-support technical knowledge resources includes extensive community forums with over 290k answered threads, over 6.5k Knowledge Base articles, over 10k demos, over 24k conceptual documentation articles, over 29k API reference articles and more than 100 new community questions answered each month.</p><hr /><h2>Want to See for Yourself?</h2><p>Our 30-day free trials include access to support. The Telerik DevCraft suite lets you pick your favorite flavor, so check it out:</p><p><a class="Btn" target="_blank" href="https://www.telerik.com/devcraft">Try DevCraft</a></p><img src="https://feeds.telerik.com/link/23068/17443642.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:9cba98fb-ee89-459e-9eae-424c5c41551f</id>
    <title type="text">Why I Finally Switched from Fiddler Classic to Fiddler Everywhere</title>
    <summary type="text">One power user explains why HTTP/2 support pushed him over the edge to upgrade to Fiddler Everywhere.</summary>
    <published>2026-09-08T13:06:11Z</published>
    <updated>2026-10-22T21:58:28Z</updated>
    <author>
      <name>Robert Boedigheimer </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23068/17442624/why-i-finally-switched-from-fiddler-classic-to-fiddler-everywhere"/>
    <content type="text"><![CDATA[<p><span class="featured">One power user explains why HTTP/2 support pushed him over the edge to upgrade to Fiddler Everywhere.</span></p><p>I have been using Fiddler for more than 20 years. I rely on it daily to identify issues, troubleshoot problems, review performance, and understand how websites work. It is amazing the difference it makes when you can see exactly how web browsers and servers communicate, and what they send and receive.</p><p>When Fiddler Everywhere was first introduced to the Progress Telerik toolset, I continued to use Fiddler Classic primarily because I did not need a cross-platform solution and Fiddler Everywhere did not yet have some of the advanced functionality I relied on. I was happy where I was with Classic.</p><p>Eventually, as features were added to Fiddler Everywhere, I started to use it more often. Fiddler Everywhere also introduced features that Classic didn&rsquo;t have, but those were also not primary features I needed, so I wasn&rsquo;t yet a full convert. The tipping point for me transitioning to Everywhere was the impact of a new protocol HTTP/2.</p><h2 id="what-is-protocol-http2-vs.-http1.1">What Is Protocol HTTP/2 vs. HTTP/1.1?</h2><p>Let&rsquo;s start by defining what a protocol is. A protocol is a set of rules that describe how participants communicate with one another. Prior to HTTP/2, the protocol for how browsers and servers communicate was HTTP/1.1.</p><p>In the world of computers, some things change very rapidly like new JavaScript frameworks or lately AI. HTTP protocol updates move more slowly.</p><p>HTTP/1.1 was introduced in 1999, and it was a HUGE success! It shouldered the burden of supporting all of the websites we used every day. Webpages typically require about 100 requests from an array of different hosts. HTTP/1.1 specified that browsers should open up to 2 connections to each host. Gradually modern browsers coalesced on using 6 connections per host, and each connection could handle a single request/response at a time.</p><p>That meant that if the webpage needed 30 files (images, CSS, JavaScript, etc.) from a single host, it would need to do that in sets of 6. This would limit how quickly the page could get the necessary files and show the webpage. Since it only supported a single request/response per connection, if a given file was taking a long period of time, that would limit the number of available connections for additional files. Another concern is that creating these connections takes time to start and become fully functional (lookup TCP &ldquo;3-way handshake&rdquo; and &ldquo;slow start&rdquo; for more details).</p><p>A very simple webpage demonstration is HTML that requests 10 images. In Fiddler Classic, I used the AutoResponder to create a rule that simulates a slow connection by intentionally delaying each JPG request by 8 seconds:</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-09/fiddler-classic-autoresponder.png?sfvrsn=abc96763_2" alt="Fiddler Classic delay by 8 seconds" /></p><p>When the webpage is requested, you can see that with a limited number of connections and only a single request and response on each connection how the performance of the webpage is impacted (a typical &ldquo;staircase&rdquo; timeline):</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-09/fiddler-classic-staircase.png?sfvrsn=64a2c2d9_2" alt="Fiddler Classic staircase timeline" /></p><p>That eventually became a problem for me because the behavior I saw in Fiddler Classic was no longer the behavior users experienced in their browsers. In this example, Fiddler Classic, using the HTTP/1.1 connection, will try to open 6 connections to the host. The webpage will end up taking at least 16 seconds.</p><h2 id="fiddler-everywhere-support-of-http2-means-better-accuracy">Fiddler Everywhere Support of HTTP/2 Means Better Accuracy</h2><p>At last, in 2015, the World Wide Web adopted HTTP/2, which had many innovations, but the most impactful was &ldquo;multiplexing,&rdquo; which is a fancy way to say that each connection can handle multiple requests and responses at the same time.</p><p>This meant that browsers only needed to use a single connection per host, and that a single slow request/response doesn&rsquo;t impact others. As you can imagine, a website hosted on a server that supported HTTP/2 could have a large impact on the reliability and performance of a webpage.</p><p>Support of HTTP2, which Fiddler Everywhere now offers, allows for a more accurate understanding of what actually went through the wire. The same demonstration page from above would look like this in Fiddler Everywhere:</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-09/fiddler-everywhere-http2.png?sfvrsn=3062bfed_2" alt="Fiddler Everywhere does not have the same staircase appearance" /></p><p>Since Fiddler Everywhere supports HTTP/2, you can see that all of the images were happening at the same time, and that the page finishes now in a little more than 8 seconds.</p><p>As the adoption of HTTP/2 grew, it reached the point that I could no longer rely on Fiddler Classic to accurately represent how a website would behave, and I finally moved to using Fiddler Everywhere exclusively.</p><hr /><h4>Ready to Try Fiddler Everywhere for Yourself?</h4><p>Enjoy a free trial today:</p><p><a href="https://www.telerik.com/download/fiddler-everywhere" class="Btn" target="_blank">Try Now</a></p><img src="https://feeds.telerik.com/link/23068/17442624.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:c332c468-9381-4363-b2bf-3e215b5a4798</id>
    <title type="text">What Are the Biggest Time Wasters for Web Designers &amp; Developers?</title>
    <summary type="text">Feel as though you should have plenty of time to get your work done, yet you always seem to be coming up on deadlines too quickly? If you want to remove the obstacles to success and improve your time management, keep reading.</summary>
    <published>2026-08-28T13:51:03Z</published>
    <updated>2026-10-22T21:58:28Z</updated>
    <author>
      <name>Suzanne Scacca </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23068/17433657/biggest-time-wasters-web-designers-developers"/>
    <content type="text"><![CDATA[<p><span class="featured">Feel as though you should have plenty of time to get your work done, yet you always seem to be coming up on deadlines too quickly? If you want to remove the obstacles to success and improve your time management, keep reading.</span></p><p>We&rsquo;ve all done it. We look at our calendar and list of tasks and think, &ldquo;There&rsquo;s plenty of time to get everything done.&rdquo; But next thing you know, you have a client breathing down your neck about a milestone or a coworker wondering where an asset is.</p><p>Where did the time go?</p><p>In many cases, it&rsquo;s not as simple as someone lacking time management skills. Often, there&rsquo;s something about the nature of the work in web design that prohibits focus and efficiency. Like being responsible for creative design work <em>and</em> administrative tasks. Or having to navigate all the competing wants and needs of everyone else involved.</p><p>In this post, we&rsquo;ll take a look at some of the more common time wasters and how to deal with them so you can be more efficient and effective with the time you have.</p><h2 id="common-time-wasters-in-web-design-and-development">Common Time Wasters in Web Design and Development</h2><p>Let&rsquo;s have a look at the kinds of things that have a tendency to pull you away from the work you should be doing:</p><h3 id="administrative-and-operational-tasks">Administrative and Operational Tasks</h3><p>I used to run a design agency where we installed time tracking software on everyone&rsquo;s computers. The goal was to find out why we were constantly running over budget and missing deadlines.</p><p>Aside from a few stragglers who used social media during work, what I found was that the biggest time wasters weren&rsquo;t from external distractions. Instead, I saw employees getting caught up in non-design tasks, including things like:</p><ul><li>Prepping for meetings</li><li>Attending meetings</li><li>Drawing up proposals</li><li>Replying to emails</li><li>Managing and organizing files</li><li>Troubleshooting technical issues within the company</li><li>Trying out new software/apps</li></ul><p><a target="_blank" href="https://www.freelancermap.com/blog/freelancer-hours-survey/">This 2025 survey from freelancermap</a> asked freelance designers about how many working hours they spent each week on &ldquo;non-billable&rdquo; tasks like these.</p><p>On average, around 5 of their 40 hours went to non-billable work.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/freelancermap-survey-non-billable-hours.png?sfvrsn=be646317_2" title="freelancermap survey non-billable work" alt="The freelancermap State of freelancing study in 2025 asked web designers how many working hours they spent each week that couldn’t be billed to a client. The average response was around 5 hours per week. 14% said it was more than 20% of their time, 43% said it was between 10% and 20%, and another 43% said it was less than 10%." /></p><p>Here is the full breakdown from the report:</p><ul><li>14% spent more than 20% of their time on non-billable work (more than 8 hours)</li><li>43% spent 10% to 20% (between 4 and 8 hours)</li><li>43% spent less than 10% (less than 4 hours)</li></ul><p><a target="_blank" href="https://www.atlassian.com/teams/software-development/state-of-developer-experience-2025">A 2025 Atlassian report</a> found that developers were losing even more time.</p><p>According to their research, 90% of developers lose 6 or more hours a week to administrative or operational tasks. A whopping 50% lose over 10 hours. This is attributed to organizational inefficiencies.</p><p>Five or six hours of a 40-hour workweek essentially means you&rsquo;re going to lose more than half a day to non-design work. If that number is closer to 10 hours, you can kiss an entire workday (and maybe even more) goodbye.</p><p>What&rsquo;s worse is that these operational time wasters don&rsquo;t usually happen in big chunks.</p><p>Instead, you have to jump from design/dev work to administrative tasks, and back again. <a target="_blank" href="https://ics.uci.edu/~gmark/chi08-mark.pdf">Recent research</a> suggests that this doesn&rsquo;t hugely impact the amount of time it takes to <em>complete</em> your work. However, it does greatly increase the stress, frustration, effort and time pressure. This can then impact your focus, motivation and energy levels&mdash;which can slow things down for you overall.</p><p>So, how do you fix this issue? It&rsquo;s not like you can start saying &ldquo;no&rdquo; to meetings, proposal generation, responding to client emails and so on.</p><p>Here are some practical <a target="_blank" href="https://www.telerik.com/blogs/4-project-management-strategies-help-build-professional-credibility">time and project management solutions</a> you can try:</p><ul><li>Use a task management tool to oversee and control your workload.</li><li>Add small time blocks to your schedule each day to handle things like checking emails as well as time-sensitive tasks.</li><li>Add a big time block to one or two days that allow people to schedule meetings with you or to ask for help with bigger research/prep tasks.</li><li>Avoid multi-tasking/task-switching as much as possible.</li><li>Set a priority level for each task and tackle the most important stuff first.</li><li>Automate where you can&mdash;like creating canned responses to send to clients.</li></ul><p>Although administrative tasks may be part of the job, you can&rsquo;t allow them to get in the way of the work clients are actually paying you to do. You also have to be careful about how you manage them as they can increase stress and anxiety, which can lead to burnout.</p><h3 id="collaboration-challenges">Collaboration Challenges</h3><p>Unless you&rsquo;re a solopreneur building websites and apps on your own, collaboration is an inherent part of the work you do as a web designer or developer.</p><p>On any given project, you might collaborate with management, coworkers, clients, vendors or third-party service providers. As you weave in between these different parties and interactions, the hope is that it will increase your momentum and not hinder it.</p><p>Unfortunately, collaboration can lead to some challenges when it comes to design and development. Here are some examples:</p><p><strong>The under-deliverer:</strong> Your client agrees to a lower project cost in return for providing you with all of the images and copy for the website. Without that content, you cannot begin working on the project. It&rsquo;s now been three weeks and the client continues to promise you&rsquo;ll have it soon.</p><p>If you continually run up against people who under-deliver, then change your process. With the example of the client who fails to deliver content, you could remove that as an option altogether. Instead of negotiating a lower project price (which is sometimes a red flag in and of itself), you offer a flat fee that covers the cost of creating content internally. And apply it to every client.</p><p>You could also work something into your contract about this kind of thing. For clients who fail to deliver what you need (e.g., content, feedback, milestone payments), you&rsquo;re entitled to put the project on hold and resume it at a later date.</p><p><strong>The micromanager:</strong> Your manager insists that every iteration of the app be reviewed by the design and development team before it&rsquo;s sent to the client. However, you&rsquo;re now three rounds into revisions internally and likely to have a few more once it reaches the client.</p><p><a target="_blank" href="https://www.forbes.com/sites/katevitasek/2022/03/08/why-collaboration-yields-improved-productivity-and-the-science-behind-it/">A 2022 Forbes article</a> reveals why management are so often the bottleneck when it comes to collaboration:</p><blockquote><p>&ldquo;Unfortunately, some business leaders assume that &lsquo;collaboration&rsquo; simply means spending time communicating back and forth with other team members. While email, video calls and meetings can certainly facilitate some collaborative efforts, too much of a focus on these can become a distraction from actually performing collaborative work.&rdquo;</p></blockquote><p>If you have managers or clients getting over-involved and spending more time <em>talking</em> about the work instead of doing it, then a conversation needs to be had. Suggest trying an approach that doesn&rsquo;t involve a collective review or constant oversight to see if the end result remains positive.</p><p>You of course want your entire team to be on the same page about everything. However, if these additional check-ins, meetings or review rounds are doing nothing but increasing tension within the team and wasting time, then they&rsquo;re not serving you well.</p><p><strong>The inefficient tech stack:</strong> In some agencies, design and development teams don&rsquo;t use the same platforms. Because of this, <a target="_blank" href="https://www.telerik.com/blogs/5-ways-improve-design-collaboration-handoff">design handoff</a> can take a while as specifications need to be drawn up for the developer. You might also end up with multiple rounds of questions to get clarity on what was mocked up and how it needs to be executed.</p><p>This may be the most difficult collaboration bottleneck to address as management will have to sign off on any changes. While you might not be able to change your design and development tech stack or handoff process, you could ease the situation and save time in other ways.</p><p>For example, <a target="_blank" href="https://slack.com/blog/collaboration/increase-productivity-by-30">digital collaboration tools can help increase team productivity by 30%</a>. So, even just finding a way to simplify and speed up communication between team members or clients could help you take back some of that wasted time.</p><p>As you can see, there are so many different reasons why collaboration can become a big time suck for designers and developers. To address these issues, you&rsquo;ll have to pinpoint which ones your agency suffers from the most and tailor-make a solution for it.</p><h3 id="rushing-or-overcomplicating-projects">Rushing or Overcomplicating Projects</h3><p>I think of this as a problem of extremes.</p><p><strong>The problem with rushing projects:</strong> On the one hand, you have projects that get pushed too quickly through the pipeline (or with too many other projects competing for priority). For whatever reason, the deadline is set and the creative team is left having to make it work.</p><p>So, what likely happens? They cut corners.</p><p>Maybe they do a whole lot of <a target="_blank" href="https://www.telerik.com/blogs/vibe-coding-buy-now-pay-later-fast-track-product-debt">vibe coding</a> and don&rsquo;t spend as much time reviewing, rewriting and repairing the code as they normally would.</p><p>Maybe they rush through design and development, not bothering to draw up <a target="_blank" href="https://www.telerik.com/blogs/10-cool-design-system-style-guide-examples">a style guide or design system</a>. And while the client is mostly satisfied with how the app works, they hate the color palette. So now every element needs to be reskinned and it&rsquo;s difficult to keep things straight without a set style guide.</p><p>Or it could just be the problem of completing every step at two or three times the usual speed. The product was built in record time, but now you have to make up for it with tons of revisions.</p><p>This is something called technical debt.</p><p>Technical debt is the idea that the time and money saved upfront when cutting corners will eventually have to be repaid later. You&rsquo;ve compromised the integrity of your process, which usually means making up for it by spending time debugging and revising.</p><p>Not only does technical debt tack on extra time after you&rsquo;re &ldquo;done,&rdquo; it creates added stress and also hurts team morale.</p><p>The solution to this? Pushing back on unreasonable timelines.</p><p><strong>The problem with overcomplicating projects:</strong> Now let&rsquo;s look at this from the other end of the spectrum. Instead of rushing through the job, you go overboard with it. This could be due to a perfectionist mindset and not wanting to ship the product off until you&rsquo;re 110% satisfied with it. It could also be due to someone dreaming way too big and being unable or unwilling to start small.</p><p>Now, perfectionism has its advantages. By paying close attention to details, you are less likely to get the dreaded email or call from the client to let you know they discovered a bunch of bugs, broken links and the like.</p><p>At the same time, perfectionism eventually stops being a benefit. There&rsquo;s this 2024 post called <a target="_blank" href="https://newsletter.eng-leadership.com/p/perfectionism-one-of-the-biggest">Perfectionism - One of the Biggest Productivity Killers in the Engineering Industry</a> that was co-written by Pinterest software engineer Jordan Cutler.</p><p>At the very top is this graph:</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/engineering-leadership-perfectionism-graphic.png?sfvrsn=cbad4362_2" title="Engineering Leadership perfectionism graph" alt="A graph from the Engineering Leadership blog shows how Value increases the more time you spend on a software engineering project, but only to a certain point. Close to the top of the line is a point where “it’s good enough”. Anything after that is “Perfectionism” and the amount of value added plateaus." /></p><p>It&rsquo;s a spot-on depiction of how perfectionism so often gets in the way.</p><p>As you spend more time working on the design and development of a product, it naturally follows that what you build will turn out good. However, you&rsquo;re eventually going to reach a point where the product and experience is good <em>enough</em>. When ego and perfectionism push you past that point, the value you get in return will be negligible.</p><p>Similar time wastage happens when trying to fulfill someone&rsquo;s lofty dreams. But not for the same reasons.</p><p>Let&rsquo;s say you&rsquo;re building a new social media app. Your agency pitched the concept to the client as the old school Facebook experience. However, now that the project is underway, the client wants to know why there aren&rsquo;t Reels, Stories and Lives. And why there isn&rsquo;t a Marketplace area or a place for Dating. They insist that you build all these features in.</p><p>Instead of running with a <a target="_blank" href="https://www.telerik.com/blogs/why-you-should-be-building-mvps">minimum viable concept</a> (MVP) to see if your target users want a scaled-back, old-school social media experience, you spend tons of time building out a Facebook lookalike. And guess what? Your users don&rsquo;t want all of that &hellip; because they already <em>have</em> it. So, now you have to cut out the features you hadn&rsquo;t initially included in order to see if your original concept works.</p><p>MVPs allow you to be smarter with your time. By starting with something basic, it gives you the time and space to incrementally enhance the experience over time. And it allows you to slowly trickle out new concepts and features to users, using their valuable input to carefully shape the experience to their actual needs.</p><h2 id="wrapping-up">Wrapping Up</h2><p>In many cases in web design and development, you&rsquo;re working on a strict time table. It&rsquo;s very rare that a project will fall in your lap and the assignee or client says, &ldquo;Take your time.&rdquo;</p><p>The problem is, there are some aspects of your job that inherently &ldquo;waste&rdquo; time. By that I mean tasks that contribute to the success of your projects as well as the organization, but that somehow slow down your web design or development work.</p><p>And with wasted time comes unfortunate consequences like:</p><ul><li>Missed deadlines</li><li>Loss of client trust</li><li>Lower profit margins</li><li>Increased stress and burnout</li><li>Lower team morale</li></ul><p>If you find that any of the things I mentioned above have become an impediment to getting things done on time, there are ways to lessen the side effects. Setting aside time for administrative chores is one way. Streamlining communications is another. And taking a more realistic approach to project completion is yet another.</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">3 Tricks to Help You Stop Procrastinating</h4></div><div class="col-8"><p class="u-fs16 u-mb0">You have a lot on your plate. But rather than get any of it done, you seek out distractions. If you find yourself <a target="_blank" href="https://www.telerik.com/blogs/3-tricks-help-stop-procrastinating">procrastinating at work, this post has three tips</a> to help you break this pattern.</p></div></div></aside><img src="https://feeds.telerik.com/link/23068/17433657.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:4696baea-52cd-4e5f-b14b-8051d7ccbea9</id>
    <title type="text">Build, Automate and Observe: What You Can Do with Telerik, Kendo UI and AI Engineering This August</title>
    <summary type="text">See what you can do with this August’s updates across Progress Telerik, Kendo UI, Telerik Reporting, Fiddler and AI Observability.</summary>
    <published>2026-08-19T17:28:37Z</published>
    <updated>2026-10-22T21:58:28Z</updated>
    <author>
      <name>Iva Borisova </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23068/17423147/build-automate-observe-august"/>
    <content type="text"><![CDATA[<p><span class="featured">See what you can do with this August&rsquo;s updates across Progress Telerik, Kendo UI, Telerik Reporting, Fiddler and AI Observability.</span></p><p>AI stopped being &ldquo;the thing that writes your code&rdquo; a while ago. At this point, it&rsquo;s showing up at nearly every stage of the job: helping you build the app, automate the parts nobody wants to do by hand and keep an eye on how AI-powered features actually behave once real users touch them.</p><p>This month&rsquo;s updates across Progress Telerik, Kendo UI, Telerik Reporting, Fiddler and Progress AI Observability are a good snapshot of that shift. Here&rsquo;s what&rsquo;s new and, more importantly, what it actually lets you do.</p><p>But before that &hellip; We&rsquo;re walking through everything below on <strong>Progress AI Monthly: Telerik, Kendo UI &amp; More livestream on August 28, 9 a.m. ET.</strong> No sign-up, just show up.</p><p><a target="_blank" href="https://www.youtube.com/watch?v=yAdEAoDZTAc">YouTube</a> &middot; <a target="_blank" href="https://www.linkedin.com/events/7495480741330386945/">LinkedIn</a> &middot; <a target="_blank" href="https://www.twitch.tv/codeitlive">Twitch</a></p><h2 id="build">Build</h2><h3 id="more-ai-fewer-tokens">More AI, Fewer Tokens</h3><p>Before getting to what&rsquo;s new, a word on what&rsquo;s gotten leaner: token consumption is down by up to 45% across our .NET products, with better output quality on top of that, so the same prompt now costs you less and gets you more. Alongside that, the Agentic UI Generator for Telerik UI for Blazor, KendoReact and Kendo UI for Angular is now available as an agent-native skill instead of an externally configured tool, which makes it noticeably easier to deploy in air-gapped and regulated environments.</p><p><a target="_blank" href="https://www.telerik.com/mcp-servers">See the MCP servers page.</a></p><h3 id="build-ui-with-a-prompt">Build UI with a Prompt</h3><p>The Agentic UI Generator now supports Telerik UI for ASP.NET Core. Describe what you need in plain language, and it generates real, framework-aware code that follows the patterns you&rsquo;d already be using: grids, forms, navigation, layout, the works. That means less time assembling scaffolding and more time on the parts of the app that actually need your judgment.</p><p>A few things people are already using it for: business applications, admin portals, customer-facing dashboards, analytics views and data-driven workflows.</p><p><a target="_blank" href="https://www.telerik.com/aspnet-core-ui/documentation/ai-core/agentic-ui-generator/overview">See how the ASP.NET Core UI Generator works.</a></p><h3 id="build-reports-with-natural-language">Build Reports with Natural Language</h3><p>The Telerik Reporting AI Report Generator in Web Report Designer has already been generating charts and gauges from a plain description of what you want to see. Now that same idea extends to structured report content: tables, lists, crosstabs, summary rows, headers and footers, and grouped structures.</p><p>Describe the table you need. For instance, grouped by region, with subtotals. And the Generator builds it, then hands you a preview to check before anything actually lands in the report.</p><p><a target="_blank" href="https://demos.telerik.com/reporting/ai-report-generator?mode=ai-table-generator">Explore AI-Generated Tables, Lists and Crosstabs.</a></p><h3 id="build-faster-with-new-components">Build Faster with New Components</h3><p>Not every update this month is AI-flavored. Some of it is just useful, new UI:</p><ul><li>Telerik UI for Blazor: <a target="_blank" href="https://demos.telerik.com/blazor-ui/expansionpanel/overview">Expansion Panel</a></li><li>KendoReact: <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/inputs/otpinput">OTP Input</a></li><li>Kendo UI for Vue: <a target="_blank" href="https://www.telerik.com/kendo-vue-ui/components/barcodes">Barcode</a>, <a target="_blank" href="https://www.telerik.com/kendo-vue-ui/components/pdfviewer">PDF Viewer</a></li><li>Telerik UI for .NET MAUI: <a target="_blank" href="https://www.telerik.com/maui-ui/documentation/controls/charts/overview">Charts (Preview)</a>, <a target="_blank" href="https://www.telerik.com/maui-ui/documentation/controls/circularslider/overview">Circular Slider</a></li><li>Telerik UI for WPF: <a target="_blank" href="https://www.telerik.com/products/wpf/documentation/controls/radbuttons/features/smart-paste-button">Smart Paste Button</a>, <a target="_blank" href="https://www.telerik.com/products/wpf/documentation/controls/radinlineaiassistant/overview">Inline AI Assistant</a></li><li>Telerik UI for WinForms: <a target="_blank" href="https://www.telerik.com/products/winforms/documentation/controls/smartpastebutton/overview">Smart Paste Button</a></li></ul><h3 id="build-ai-ready-experiences-with-the-llm-kit">Build AI-ready Experiences with the LLM Kit</h3><p>The LLM Kit isn&rsquo;t just something you bolt on after the fact to keep tabs on your agents. It&rsquo;s a set of components for building the AI-powered experience itself. Chat-style interfaces, multi-step agent workflows, tool-call displays, approval steps, citations, checkpoints: the pieces you&rsquo;d otherwise have to design and build from scratch are already there, ready to wire into your app.</p><p>It gives you a real head start on shipping AI features that feel considered rather than bolted on, and, as a side benefit, your users and your team can actually see what the AI is doing along the way.</p><p>It&rsquo;s available now for <a target="_blank" href="https://demos.telerik.com/blazor-ui/llmkit/overview">Telerik UI for Blazor</a>, <a target="_blank" href="https://demos.telerik.com/aspnet-core/llm-kit">UI for ASP.NET Core</a>, <a target="_blank" href="https://demos.telerik.com/aspnet-mvc/llm-kit">UI for MVC</a>, <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/conversational-ui/llm-kit">Kendo UI for Angular</a>, <a target="_blank" href="https://demos.telerik.com/kendo-ui/llm-kit/index">UI for jQuery</a> and <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/conversationalui/llm-kit">KendoReact</a>.</p><h2 id="automate">Automate</h2><h3 id="automate-application-upgrades">Automate Application Upgrades</h3><p>Nobody enjoys upgrading a codebase, and it&rsquo;s rarely the highest-value use of anyone&rsquo;s afternoon. AI-assisted migration is now available for Telerik UI for ASP.NET Core, UI for MVC, UI for WPF, KendoReact and Kendo UI for jQuery through the Upgrade Assistant (part of the MCP engine). It handles the changes it already knows how to make on its own and walks you through the ones that need a human decision, so version bumps stop eating whole sprints.</p><h3 id="automate-reporting-operations-with-ai-agents">Automate Reporting Operations with AI Agents</h3><p>Telerik Report Server now ships with a native MCP Server, so assistants like Copilot, Claude, Cursor and Codex can work directly inside your reporting environment instead of going through a custom integration you&rsquo;d have to build and maintain. Once you&rsquo;ve set up a Personal Access Token, agents can:</p><ul><li>Generate reports</li><li>Manage data sources</li><li>Schedule reports</li><li>Handle general reporting-environment admin</li></ul><p><a target="_blank" href="https://www.telerik.com/report-server/documentation/dotnet-docs/mcp-server">See Report Server MCP docs.</a></p><h3 id="automate-debugging-with-fiddler-mcp">Automate Debugging with Fiddler MCP</h3><p>Debugging gets a lot more useful when your AI assistant can see what actually happened over the wire, not just what the code says should happen. Fiddler MCP connects your coding assistant to real traffic captured in Fiddler Everywhere, actual requests, responses, headers, status codes, so it&rsquo;s reasoning from evidence instead of guessing. Set it up with one click inside Fiddler Everywhere, then go from &ldquo;something&rsquo;s broken&rdquo; to a fix without leaving your IDE.</p><p><a target="_blank" href="https://www.telerik.com/fiddler/fiddler-everywhere/mcp">Explore Fiddler Everywhere MCP.</a></p><h3 id="automate-licensing-for-cicd">Automate Licensing for CI/CD</h3><p>Automation isn&rsquo;t only about what your agents do. It&rsquo;s also about what shouldn&rsquo;t need a human to manage. Deployment Keys give you a dedicated way to license automated builds and deployments: each key ties to a registered application and the exact Telerik or Kendo products it uses, so tracking, monitoring and reporting on product usage across your CI/CD environments gets a lot less manual.</p><h2 id="observe">Observe</h2><p>Building faster and automating more only gets you so far if you can&rsquo;t tell what your AI systems are actually doing once they&rsquo;re live. That&rsquo;s the part teams tend to underinvest in until something goes wrong.</p><h3 id="learn-why-ai-observability-matters">Learn Why AI Observability Matters</h3><p>If you want to go deeper on this, join Jeff Fritz and Progress Product Manager Lyubomir Atanasov on <strong>August 26</strong> for <strong>&ldquo;AI Observability: When AI Goes Off the Rails.&rdquo;</strong> They&rsquo;ll walk through some genuinely memorable public AI failures, then run a live Progress AI Observability demo covering agent tracing, drift and hallucination detection, and performance and cost monitoring.</p><p><a target="_blank" href="https://www.telerik.com/webinars/progress-telerik/when-ai-goes-off-the-rails">Register for the webinar.</a></p><h3 id="help-shape-the-future-of-agentic-development">Help Shape the Future of Agentic Development</h3><p>This is also the thinking behind the Progress Agent Harness Early Access Program: structured, auditable AI workflows, with early adopters getting a direct line to the roadmap. As agents take on more, being able to manage them predictably matters just as much as what they can build.</p><p><a target="_blank" href="https://www.telerik.com/agent-harness">Join the Agent Harness EAP.</a></p><h2 id="what’s-new-and-release-history">What&rsquo;s New and Release History</h2><p>To see everything that&rsquo; is new in August 2026 release edition, visit the <a target="_blank" href="https://www.telerik.com/support/whats-new">What&rsquo;s New in Telerik and Kendo UI page</a>. For a deeper dive into each product, follow the links below.</p><table><style>table, th, td {
  border: 1px;
  border-color: #bdbdba;
  border-style: dotted;
  border-collapse: collapse;
  margin-right: auto;
  padding:0in 5.4pt 0in 5.4pt;
  text-align :left;
}
</style>
<thead><tr><th><strong>Product</strong></th><th><strong>What&rsquo;s New</strong></th><th><strong>Release History</strong></th></tr></thead><tbody><tr><td>Kendo UI for Angular</td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/kendo-angular-ui/2026-q3">What&rsquo;s New in Kendo UI for Angular</a></td><td><a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/changelogs/kendo-angular-ui#v25.1.0-develop.4">Kendo UI for Angular Release History</a></td></tr><tr><td>KendoReact</td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/kendo-react-ui/2026-q3">What&rsquo;s New in KendoReact</a></td><td><a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/changelogs/ui-for-react#v16.1.0-develop.2">KendoReact Release History</a></td></tr><tr><td>Kendo UI for Vue</td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/kendo-vue-ui/2026-q3">What&rsquo;s New in Kendo UI for Vue</a></td><td><a target="_blank" href="https://www.telerik.com/kendo-vue-ui/components/changelogs/ui-for-vue#v9.2.0">Kendo UI for Vue Release History</a></td></tr><tr><td>Kendo UI for jQuery</td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/kendo-jquery-ui/2026-q3">What&rsquo;s New in Kendo UI for jQuery</a></td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/kendo-ui/release-history/kendo-ui-for-jquery-2026-3-811-(2026-q3)">Kendo UI for jQuery Release History</a></td></tr><tr><td>Telerik UI for Blazor</td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/blazor-ui/2026-q3">What&rsquo;s New in Telerik UI for Blazor</a></td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/blazor-ui/release-history/telerik-ui-for-blazor-15-0-0-(2026-q3)">Telerik UI for Blazor Release History</a></td></tr><tr><td>Telerik UI for ASP.NET Core</td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/aspnet-core-ui/2026-q3">What&rsquo;s New in Telerik UI for ASP.NET Core</a></td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/aspnet-core-ui/release-history/telerik-ui-for-asp-net-core-2026-3-811-(2026-q3)">Telerik UI for ASP.NET Core Release History</a></td></tr><tr><td>Telerik UI for ASP.NET MVC</td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/aspnet-mvc/2026-q3">What&rsquo;s New in Telerik UI for ASP.NET MVC</a></td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/aspnet-mvc/release-history/telerik-ui-for-asp-net-mvc-2026-3-811-(2026-q3)">Telerik UI for ASP.NET MVC Release History</a></td></tr><tr><td>Telerik UI for .NET MAUI</td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/maui-ui/2026-q3">What&rsquo;s New in Telerik UI for .NET MAUI</a></td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/maui-ui/release-history/telerik-ui-for-net-maui-15-0-0-(2026-q3)">Telerik UI for .NET MAUI Release History</a></td></tr><tr><td>Telerik UI for WPF</td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/wpf/2026-q3">What&rsquo;s New in Telerik UI for WPF</a></td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/wpf/release-history/telerik-ui-for-wpf-2026-3-812-(2026-q3)">Telerik UI for WPF Release History</a></td></tr><tr><td>Telerik UI for WinForms</td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/winforms/2026-q3">What&rsquo;s New in Telerik UI for WinForms</a></td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/winforms/release-history/telerik-ui-for-winforms-2026-3-812-(2026-q3)">Telerik UI for WinForms Release History</a></td></tr><tr><td>Telerik Reporting</td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/reporting/2026-q3">What&rsquo;s New in Telerik Reporting</a></td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/reporting/release-history/progress-telerik-reporting-2026-q3-(20-2-26-812)">Telerik Reporting Release History</a></td></tr><tr><td>Telerik Report Server</td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/report-server/2026-q3">What&rsquo;s New in Telerik Report Server</a></td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/report-server/release-history/progress-telerik-report-server-2026-q3-(12-2-26-812)">Telerik Report Server Release History</a></td></tr><tr><td>Telerik Document Processing</td><td><a target="_blank" href="https://www.telerik.com/document-processing-libraries">What&rsquo;s New in Telerik DPL</a></td><td><a target="_blank" href="https://www.telerik.com/support/whats-new/telerik-document-processing/release-history/progress-telerik-document-processing-2026-3-805">Telerik DPL Release History</a></td></tr></tbody></table><img src="https://feeds.telerik.com/link/23068/17423147.gif" height="1" width="1"/>]]></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-10-22T21:58:28Z</updated>
    <author>
      <name>Dragan Grigorov </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23068/17357615/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://www.telerik.com/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://www.telerik.com/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-and-Create-IdP-Group">4. Configure SCIM in Your Identity Provider and Create IdP Group</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://www.telerik.com/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://www.telerik.com/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><ul><li>Create an IdP group with a clear and distinctive name that corresponds to your license, e.g. &ldquo;Telerik DCU Subscription,&rdquo; and begin adding users to it. If your Telerik license already has seated users assigned, make sure to include those users in the IdP group before enabling SCIM provisioning.</li></ul><p>Once SCIM provisioning is enabled, all users in the selected IdP group will be synchronized with the associated license. If developers are already assigned to a Telerik license but the corresponding IdP group is empty, the license assignments will be updated to match the IdP group membership, which will remove all currently assigned developers. To avoid any disruption, verify that the users in the IdP group match the existing Telerik license assignments before enabling SCIM provisioning. ⚠️</p><p><img sf-image-responsive="true" src="https://www.telerik.com/sfimages/default-source/.net-maui-aiprompt/scim-idp.png?sfvrsn=c39975f9_2" height="1179" style="max-width:100%;height:auto;" title="scim-idp" width="1783" alt="SCIM group subscription" sf-size="407244" /></p><h3 id="enable-provisioning-and-map-your-devcraft-license-to-your-idp-group">5. Enable Provisioning and 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>Once SCIM provisioning is configured and enabled, an IdP Group Name dropdown will become available, allowing you to select the group configured in your identity provider.</li></ul><p><img sf-image-responsive="true" src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-08/scim-group-name.png?sfvrsn=9c8664cd_2" height="1324" style="max-width:100%;height:auto;" title="scim-group-name" width="1744" alt="SCIM IdP Group Name" sf-size="1918189" /></p><p> From this point on, access is controlled through group membership in your Identity Provider.</p><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://www.telerik.com/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><img src="https://feeds.telerik.com/link/23068/17357615.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:76fee4ac-a66f-4d26-a5ac-69295f6514a1</id>
    <title type="text">Creating a Custom AI Agent with Telerik Tools 6: Embedding Conversational and Invisible Agents</title>
    <summary type="text">Different kinds of AI-enabled applications need different UIs. Progress has the components you need for all of them.</summary>
    <published>2026-05-13T12:09:46Z</published>
    <updated>2026-10-22T21:58:28Z</updated>
    <author>
      <name>Peter Vogel </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23068/17339809/creating-custom-ai-agent-telerik-tools-6-embedding-conversational-invisible-agents"/>
    <content type="text"><![CDATA[<p><span class="featured">For some AI-enabled applications, your user will want to have a conversation with your AI agent. Other times, your user will want your agent to stay out of the way until needed. With the right UI component, you can handle both kinds of applications.</span></p><p>By themselves, AI agents can be useful. Integrated into an application, however, a custom AI agent can provide users with the ability to navigate the application and to better understand the business area that the application is part of.</p><p>Progress provides multiple tools for embedding AI agents into your application&rsquo;s UI. In previous posts, for example, I&rsquo;ve looked at Progress Telerik and Kendo UI AI Prompt components in JavaScript [in post 5](Creating a Custom AI Agent with Telerik Tools: Creating an Interactive UI in JavaScript) and Blazor [in post 4](Creating a Custom AI Agent with Telerik Tools: Crafting an Interactive Blazor UI). In this post, I&rsquo;m going to address two different UI scenarios where the AI Prompt might not be your best choice.</p><p>The first scenario is when your AI agent <em>isn&rsquo;t</em> the point of your application&mdash;it&rsquo;s just a useful tool that your users can invoke when they need it. The AI Prompt component isn&rsquo;t necessarily your best choice there because, by default, the AI Prompt takes over a significant portion of your application&rsquo;s UI (though you can customize that using the AI Prompt&rsquo;s various templates). Out of the box, however, the <a target="_blank" href="https://www.telerik.com/kendo-jquery-ui/documentation/controls/inline-aiprompt/overview">Inline AIPrompt</a> provides a compact interface that users can bring up when they need your agent and, equally important, dismiss it when they don&rsquo;t need your agent.</p><p>The AI Prompt component also assumes a specific workflow, with the user alternatively entering a prompt and viewing the current output from your agent (along with a history of responses to previous prompts). But when the interaction with your AI agent <em>is</em> the point of your application, you might want to create more natural back-and-forth conversational interaction between your user and the agent. The Telerik Chat component will let you do that, again, out of the box.</p><p>I&rsquo;m going to show how to use both in this post.</p><p>By the way (which means you can skip this paragraph&mdash;it&rsquo;s all background): In this post, I&rsquo;m assuming the existence of a custom AI agent that provides application-specific support to your users. That means that you&rsquo;ve [selected a Large Language Model](reference to Creating a Custom AI Agent with Telerik Tools: Configuring an LLM for Azure or Ollama) (LLM) for processing your user&rsquo;s input, loaded your own content into your [custom AI agent](reference to Creating a Custom AI Agent with Telerik Tools: Loading and Accessing Your Agent&rsquo;s Content) (about 10 lines of code) and written another 10 lines of code to tie your agent into your application. For a JavaScript application, you&rsquo;ll need to wrap that code in a web service (I covered the, literally, two lines of code to make that happen at the start of a [post on using the AI Prompt component from JavaScript](reference to Creating a Custom AI Agent with Telerik Tools: Creating an Interactive UI in JavaScript)) and write the five lines of JavaScript code to all your agent. That probably sounds like a lot. It is, however, only about two dozen lines of code to give you an AI-enabled application with domain-specific knowledge.</p><h2 id="letting-the-user-access-your-ai-agent-when-they-want-it">Letting the User Access Your AI Agent When They Want It</h2><p>I&rsquo;m going to start with the scenario where you want to enable your user to invoke your AI agent when they need it and ignore the agent the rest of the time. The Inline AIPrompt lets you do that: implement a minimal user interface that pops up when the user needs to send a prompt to your agent and then review your agent&rsquo;s response:</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-03/aiprompt-javascript-7---inline-aiprompt.png?sfvrsn=9294e5a3_2" alt="A screenshot of a Web page discussing creating asynchronous applications with Azure Storage queues with a dialog box hovering over it. The dialog box has a textbox at the bottom where the user has typed in a question (“What code is required to write a message?”). Above the textbox, a panel displays the code." /></p><p>I&rsquo;m going to use the <a target="_blank" href="https://www.telerik.com/kendo-jquery-ui/jquery-inline-ai-prompt">Kendo UI for jQuery Inline AIPrompt</a> in this case study, but there are also versions of the component for <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/conversationalui/inline-ai-prompt/getting-started">React</a>, <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/angular-inline-ai-prompt">Angular</a>, <a target="_blank" href="https://www.telerik.com/aspnet-core-ui/inline-ai-prompt">ASP.NET Core</a>, <a target="_blank" href="https://www.telerik.com/aspnet-mvc/inline-ai-prompt">ASP.NET MVC</a> and <a target="_blank" href="https://www.telerik.com/blazor-ui/documentation/components/inlineaiprompt/overview">Blazor</a>.</p><h3 id="configuring-the-inline-ai-prompt">Configuring the Inline AI Prompt</h3><p>Your first step is to add the Inline AIPrompt to your webpage with a <code>&lt;div&gt;</code> element that has its <code>id</code> attribute set to some name (I used <code>customAgent</code>):</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>customAgent<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
</code></pre><p>You can place that <code>&lt;div&gt;</code> element wherever you want in your page&rsquo;s body (I put it near the top of my application&rsquo;s HTML, but it really doesn&rsquo;t matter).</p><p>Next, you need to configure your Inline AIPrompt component. You do that by writing a jQuery command to find your <code>&lt;div&gt;</code> element, passing the name you put in the <code>&lt;div&gt;</code> element&rsquo;s <code>id</code> attribute.</p><p>You then call the <code>kendoInlineAIPrompt</code> function from the retrieved element, passing a configuration object. The <code>kendoInlineAIPrompt</code> function returns an object and you then call that object&rsquo;s <code>data</code> function, passing the string <code>kendoInlineAIPrompt</code>. That, in turn, will return an object you can use to manage the component.</p><p>You only want to do this configuration once, so you should tuck that code into jQuery&rsquo;s ready function. That means you&rsquo;ll end up with code like this to set up your component:</p><pre class=" language-javascript"><code class="prism  language-javascript"><span class="token function">$</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>
  <span class="token function">$</span><span class="token punctuation">(</span><span class="token string">"#customAgent"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">kendoInlineAIPrompt</span><span class="token punctuation">(</span>
    <span class="token punctuation">{</span>
      <span class="token comment">//&hellip;configuration object</span>
     <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">data</span><span class="token punctuation">(</span><span class="token string">"kendoInlineAIPrompt"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>In the customization object that you pass to the <code>kendoInlineAIPrompt</code> function, you need to set two properties on the object:</p><ul><li><code>isStreaming</code>: Set this to <code>true</code> to let you update the component&rsquo;s UI with the output from your custom AI agent</li><li><code>promptRequest</code>: Set this to a function to be called when your user enters a prompt and clicks the &ldquo;go&rdquo; button in the Inline AIPrompt&rsquo;s UI</li></ul><p>By the way, you don&rsquo;t have to put a function in the <code>promptRequest</code> property. You can configure the Inline AIPrompt to <a target="_blank" href="https://www.telerik.com/kendo-jquery-ui/documentation/controls/inline-aiprompt/get-started">call an LLM directly</a> by setting your configuration object&rsquo;s <code>service</code> and <code>systemPrompt</code> properties. Using the <code>promptRequest</code> function, however, simplifies calling a custom AI agent.</p><p>A minimal implementation might look like this:</p><pre class=" language-javascript"><code class="prism  language-javascript"><span class="token keyword">let</span> ca <span class="token operator">=</span> <span class="token function">$</span><span class="token punctuation">(</span><span class="token string">"#customAgent"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">kendoInlineAIPrompt</span><span class="token punctuation">(</span>
 <span class="token punctuation">{</span>
   isStreaming<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
   promptRequest<span class="token punctuation">:</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token punctuation">(</span>req<span class="token punctuation">)</span> <span class="token punctuation">{</span>
     <span class="token comment">//&hellip;function code           </span>
   <span class="token punctuation">}</span>
 <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">data</span><span class="token punctuation">(</span><span class="token string">"kendoInlineAIPrompt"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>         
</code></pre><p>Your <code>promptRequest</code> function will be passed a parameter. From that parameter, you can retrieve the user&rsquo;s prompt by reading a property cleverly called <code>prompt</code>. Once you have the user&rsquo;s prompt, you can pass it to your agent and catch your agent&rsquo;s response. In my case, that means passing the prompt to my JavaScript function that calls my AI agent&rsquo;s web service.</p><p>Once you get a response from your agent, you need to add it to the Inline AIPrompt&rsquo;s UI. To do that, you just need call the component&rsquo;s <code>updatePromptOutputContent</code> function, passing the output from your agent.</p><p>Since the JavaScript function that calls my agent is called <code>asyncAppAgent</code> and returns the agent&rsquo;s response as a string ready to add to the Inline AIPrompt&rsquo;s UI, my <code>promptResult</code> function is a single line of code (my web service function is asynchronous so I have to use the <code>await</code> keyword when calling it and flag the function as <code>async</code>):</p><pre class=" language-javascript"><code class="prism  language-javascript">ca <span class="token operator">=</span> <span class="token function">$</span><span class="token punctuation">(</span><span class="token string">"#customAgent"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">kendoInlineAIPrompt</span><span class="token punctuation">(</span>
  <span class="token punctuation">{</span>
    isStreaming<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
    promptRequest<span class="token punctuation">:</span> <span class="token keyword">async</span> <span class="token keyword">function</span><span class="token punctuation">(</span>req<span class="token punctuation">)</span> 
      <span class="token punctuation">{</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">updatePromptOutputContent</span><span class="token punctuation">(</span>                                                       
          <span class="token keyword">await</span> <span class="token function">asyncAppAgent</span><span class="token punctuation">(</span>req<span class="token punctuation">.</span>prompt<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">data</span><span class="token punctuation">(</span><span class="token string">"kendoInlineAIPrompt"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> 
</code></pre><p>There&rsquo;s more you can do with the component (for example, as with the AI Prompt component, you can give the user some predefined commands to use with the component). But before you can do any of that&mdash;because, by default, the component isn&rsquo;t visible&mdash;you need give your users a way to display the Inline AIPrompt when they want it.</p><h3 id="opening-the-prompt">Opening the Prompt</h3><p>Since I&rsquo;m trying to minimize the UI footprint for my agent&rsquo;s interface, I decided to use the Kendo UI for <a target="_blank" href="https://www.telerik.com/kendo-jquery-ui/documentation/controls/menu/contextmenu/overview">jQuery Context Menu</a> to let the user invoke my prompt (you could just have a button on your page that opens Inline AIPrompt).</p><p>Since I was using the context menu (which implies giving the user choices), I also decided to give the user the ability to choose between two custom agents:</p><ul><li>One agent would be loaded with content about using the application (e.g., the application&rsquo;s user guide, enhanced with a download of questions and responses from the application&rsquo;s end user forum)</li><li>The other agent would be loaded with content about the application&rsquo;s topic area (in my case, that topic area is creating an asynchronous application and the content is from another series of <a target="_blank" href="https://www.telerik.com/blogs/coding-azure-18-creating-securing-azure-storage-queues">posts on Coding Azure</a> I wrote).</li></ul><p>The first step in using the context menu is to add a <code>&lt;ul&gt;</code> element anywhere on your page and set its <code>id</code> attribute to some name (in this case, I picked <code>selectAgentMenu</code>).</p><p>Within that <code>&lt;ul&gt;</code> element, you use <code>&lt;li&gt;</code> elements to define your popup menu&rsquo;s choices. I also (for reasons that will become obvious), assigned values to the <code>&lt;li&gt;</code> elements&rsquo; <code>name</code> attributes:</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>selectAgentMenu<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>li</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Ask<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Ask about asynchronouse processing<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Help<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Ask about using this application<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>Next, you need to configure your content menu, this time by calling the <code>kendoContextMenu</code> function on the <code>&lt;ul&gt;</code> element that defines your menu. In the configuration object you pass to this function, you must set its <code>select</code> property to a function that will be called when a user clicks a choice in the menu. That function is passed a parameter with an <code>item</code> property that holds the <code>&lt;li&gt;</code> element that defined the menu choice the user selected.</p><p>In this code, I use the element&rsquo;s <code>attr</code> function to retrieve the element&rsquo;s <code>name</code> attribute and use that value to set a variable I called mode. Later on, I&rsquo;ll use that mode variable in my Inline AIPrompt&rsquo;s <code>promptRequest</code> function to call the right AI agent. I then use the variable created when I configured my Inline AI Prompt component to display the component by calling the its <code>open</code> method:</p><pre class=" language-javascript"><code class="prism  language-javascript"><span class="token keyword">let</span> mode <span class="token operator">=</span> <span class="token string">""</span><span class="token punctuation">;</span>
<span class="token function">$</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> 
<span class="token punctuation">{</span>
  <span class="token function">$</span><span class="token punctuation">(</span><span class="token string">"#selectAgentMenu"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">kendoContextMenu</span><span class="token punctuation">(</span>
    <span class="token punctuation">{</span>
      select<span class="token punctuation">:</span> <span class="token keyword">function</span> <span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        mode <span class="token operator">=</span> <span class="token function">$</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>item<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">attr</span><span class="token punctuation">(</span><span class="token string">"name"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                                                
        ca<span class="token punctuation">.</span><span class="token function">open</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>           
      <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>With my context menu in place, I can enhance my <code>promptRequest</code> function to pick the AI agent, based on the user&rsquo;s menu choice:</p><pre class=" language-javascript"><code class="prism  language-javascript">promptRequest<span class="token punctuation">:</span> <span class="token keyword">async</span> <span class="token keyword">function</span><span class="token punctuation">(</span>req<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">switch</span> <span class="token punctuation">(</span>mode<span class="token punctuation">)</span>
  <span class="token punctuation">{</span>
    <span class="token keyword">case</span> <span class="token string">"Ask"</span><span class="token punctuation">:</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">updatePromptOutputContent</span><span class="token punctuation">(</span>                                                       
    <span class="token keyword">await</span> <span class="token function">asyncInfoAgent</span><span class="token punctuation">(</span>req<span class="token punctuation">.</span>prompt<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      
    <span class="token keyword">break</span><span class="token punctuation">;</span>
    <span class="token keyword">case</span> <span class="token string">"Help"</span><span class="token punctuation">:</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">updatePromptOutputContent</span><span class="token punctuation">(</span>                                                       
    <span class="token keyword">await</span> <span class="token function">appHelpAgent</span><span class="token punctuation">(</span>req<span class="token punctuation">.</span>prompt<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      
    <span class="token keyword">break</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>The user can dismiss the component by clicking anywhere on the webpage, so using the resulting interface looks like this:</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-03/integrating-ai-8---inline.gif?sfvrsn=683c9935_2" alt="An animated gif showing the user right-clicking on a web page which causes a popup menu to appear, with two menu choices. The user clicks the top menu choice which causes the menu to disappear and the Inline AIPrompt’s dialog box to appear. The user types a prompt into the textbox in the Inline AIPrompt’s UI and clicks an arrow at the right end of the textbox. A panel opens at the top of the panel with the text “Thinking…”. After a pause, the panel is filled with the response from the AI agent. The user then clicks on the page outside of the Inline AIPrompt’s dialog box and the dialog box disappears" /></p><p>At this point you have an interface to your AI agent that appears and disappears as the user needs it.</p><h2 id="conversational-ai">Conversational AI</h2><p>Sometimes, however, you want a UI that allows your user to have a conversation with your agent. You can enable <a target="_blank" href="https://demos.telerik.com/kendo-ui/chat/index">Kendo UI for jQuery Chat</a> component for that (and, again, versions of the Chat component exist for multiple other platforms). The component creates a chat UI familiar to your user, with a textbox at the bottom for your user to enter their prompt and a chat history above that to display the user&rsquo;s prompts and the agent&rsquo;s responses:</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-03/aiprompt-javascript-9---chat-aiprompt.png?sfvrsn=cf734fba_2" alt="A web page discussing creating asynchronous processes using Azure storage queues, with a chat window at the bottom. There is a conversation in progress in the chat history part of the chat window: The user earlier asked for a summary of key issues in 50 words and an agent responded with a short paragraph." /></p><p>Your first step is to decide where on your page you want your chat dialog to appear and to add a <code>&lt;div&gt;</code> element there, with the <code>&lt;div&gt;</code> element&rsquo;s <code>id</code> attribute set to some name (I used <code>dialogAgent</code>):</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>dialogAgent<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
</code></pre><p>After that, you need to configure your chat dialog by using a jQuery query to find your <code>&lt;div&gt;</code> element and call the <code>kendoChat</code> function from it, passing a configuration object. In the configuration object, you need to set its <code>sendMessage</code> property to a function to be called when your user enters a prompt. In that function, you&rsquo;ll call your agent and update the chat with your agent&rsquo;s response.</p><p>You only want to configure your chat once, so you should wrap that code in jQuery&rsquo;s ready function, like this:</p><pre class=" language-javascript"><code class="prism  language-javascript"><span class="token function">$</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> 
<span class="token punctuation">{</span>
  <span class="token function">$</span><span class="token punctuation">(</span><span class="token string">"#dialogAgent"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">kendoChat</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    sendMessage<span class="token punctuation">:</span> <span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token comment">//&hellip;function</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>Rather than using a function in the <code>sendMessage</code> property, you can configure your chat component to <a target="_blank" href="https://demos.telerik.com/kendo-ui/chat/ai-integration">call an LLM directly</a> by setting the configuration object&rsquo;s <code>aiServiceUrl</code> property. Using a function in the <code>sendMessage</code> property, however, simplifies calling a custom agent.</p><p>Now you need to write the <code>sendMessage</code> function that will send a message to your agent. You function will be passed a parameter that has two useful properties:</p><ul><li><code>message</code>: You use this property both to retrieve the user&rsquo;s prompt and to manage the display of the user&rsquo;s prompt in the chat history pane</li><li><code>sender</code>: Use this to add the response from your agent to the chat history (your user&rsquo;s prompt will be added to the chat history automatically)</li></ul><p>In your <code>sendMessage</code> function, you need to set the <code>authorName</code> property on the parameter&rsquo;s <code>message</code> property. That controls the label displayed in the chat history window with your user&rsquo;s prompts. I picked <code>Me</code>:</p><pre class=" language-javascript"><code class="prism  language-javascript"><span class="token function">$</span><span class="token punctuation">(</span><span class="token string">"#dialogAgent"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">kendoChat</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  sendMessage<span class="token punctuation">:</span> <span class="token punctuation">(</span>c<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  c<span class="token punctuation">.</span>message<span class="token punctuation">.</span>authorName <span class="token operator">=</span> <span class="token string">"Me"</span><span class="token punctuation">;</span>
  <span class="token comment">//&hellip;more</span>
<span class="token punctuation">}</span>
</code></pre><p>You can retrieve the user&rsquo;s prompt from the <code>text</code> property on the object in the parameter&rsquo;s <code>message</code> property. You then pass that prompt to your agent (I used the JavaScript function I wrote to call my agent&rsquo;s web service) and catch your agent&rsquo;s response.</p><p>To add the agent&rsquo;s response to the chat history, you call the <code>postMessage</code> of the object in the <code>sender</code> property of the parameter passed to your function. You pass the <code>postMessage</code> property an object with three properties set:</p><ul><li><code>authorId</code>: A unique identifier to distinguish your user&rsquo;s prompt from your agent&rsquo;s responses</li><li><code>authorName</code>: The name that will be displayed with the agent&rsquo;s response in the chat history</li><li><code>text</code>: The response from your agent</li></ul><p>That code is also pretty short:</p><pre class=" language-javascript"><code class="prism  language-javascript">sendMessage<span class="token punctuation">:</span> <span class="token keyword">async</span> <span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  e<span class="token punctuation">.</span>message<span class="token punctuation">.</span>authorName <span class="token operator">=</span> <span class="token string">"Me"</span><span class="token punctuation">;</span>
  e<span class="token punctuation">.</span>sender<span class="token punctuation">.</span><span class="token function">postMessage</span><span class="token punctuation">(</span>
  <span class="token punctuation">{</span>
    authorId<span class="token punctuation">:</span> <span class="token string">"Agent"</span><span class="token punctuation">,</span>
    authorName<span class="token punctuation">:</span> <span class="token string">"Agent"</span><span class="token punctuation">,</span>
    text<span class="token punctuation">:</span> <span class="token keyword">await</span> <span class="token function">asyncInfoAgent</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>message<span class="token punctuation">.</span>text<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>Your user can now interactively query your AI agent to extract whatever information you need.</p><p>There&rsquo;s more you can do here (creating a context-aware conversation by using state management to store message history and including that in your user&rsquo;s requests, for example). But, even with this simple implementation, you&rsquo;re ready to let your user have a conversation with your agent.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-03/intergating-ai-10---chat.gif?sfvrsn=c220c1bb_2" alt="An animated GIF showing a web page with an embedded chat window. The user enters “Summarize in 50 words” in the textbox at the bottom of the chat window and clicks the arrow at the right end of the textbox. The user’s prompt appears in the chat history pane, there is a pause of three or four sections, and then the AI agent’s response appears in the chat history pane, on the opposite side of the pane, in a different color, and labelled “Agent”" /></p><p>There are, at least, three scenarios when you&rsquo;ll want to integrate an AI-enabled component into your application&rsquo;s UI: when the interaction with your agent is the primary point of your application, when your AI agent is not the primary part by &ldquo;just another part&rdquo; of your application, and when your users will only occasionally need to access your agent (and these are just points on a continuum).</p><p>All three of the Progress Telerik and Kendo UI &ldquo;UI for AI&rdquo; components&mdash;Chat, AIPrompt and Inline AIPrompt&mdash;support those three scenarios (and all the points in between). Picking the right component, however, will simplify your code and let you get your application up and running 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">Get Access to All These Components and More</h4></div><div class="col-8"><p class="u-fs16 u-mb0">The free 30-day trial of <a target="_blank" href="https://www.telerik.com/devcraft">Telerik DevCraft</a> lets you really kick the tires for yourself. <a target="_blank" href="https://www.telerik.com/try/devcraft-ultimate">Try it today!</a></p></div></div></aside><img src="https://feeds.telerik.com/link/23068/17339809.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:bb2fa99e-0c29-407f-9eb9-4d2b43a9632a</id>
    <title type="text">Creating More Realistic Tests with In-Memory Databases in ASP.NET Core</title>
    <summary type="text">Testing ASP.NET Core APIs with in-memory SQLite and JustMock enables validation of real-world scenarios like pagination, keys and rules without a real database.</summary>
    <published>2026-05-06T17:20:57Z</published>
    <updated>2026-10-22T21:58:28Z</updated>
    <author>
      <name>Assis Zang </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23068/17335637/creating-more-realistic-tests-memory-databases-aspnet-core"/>
    <content type="text"><![CDATA[<p><span class="featured">Testing ASP.NET Core APIs with in-memory SQLite and JustMock enables validation of real-world scenarios like pagination, keys and rules without a real database.</span></p><p>Testing a web API involves much more than simply checking whether methods return or send data correctly. It requires validating real-world behaviors like pagination, unique keys, data persistence and other database rules. Anticipating and reproducing these scenarios can be challenging, especially when they depend on specific query behaviors or stored data.</p><p>To address this, the .NET ecosystem provides powerful tools for unit testing. In this post, we explore how in-memory SQLite serves as an effective solution for testing ASP.NET Core APIs, enabling developers to simulate common scenarios without deploying a real database. Additionally, we demonstrate how tools like Progress Telerik JustMock can streamline test implementation and help validate business rules efficiently.</p><h2 id="the-importance-of-testing-in-real-world-environments">The Importance of Testing in Real-World Environments</h2><p>A common example of functionality in web APIs is paginated search, which at first glance is quite simple. We receive parameters such as <code>page</code> and <code>pageSize</code>, apply a Skip and a Take, return the data and move on. In a controlled scenario, with few records, it&rsquo;s difficult for something to go wrong.</p><p>The problem begins when this code reaches production or even test environments, without having been validated with a real database. Consider a common scenario: in a paginated search, sorting is essential for it to function correctly. Without an explicit <code>OrderBy</code>, the database does not guarantee the order of the returned records.</p><p>In in-memory lists, the result usually appears stable, which completely masks the problem. Without tests running this query on a relational database, the error goes unnoticed, resulting in inconsistent pagination with duplicate records between pages, items &ldquo;disappearing&rdquo; when navigating between pages or different results for the same request. This type of bug is especially difficult to reproduce locally when not using a real database or even a simulation of one.</p><p>This is where the in-memory database plays a very important role. It allows the query to be executed exactly as it will be executed in production, revealing flaws that only appear when pagination, sorting and the database work together.</p><h2 id="understanding-in-memory-sqlite">Understanding In-Memory SQLite</h2><p>In-memory SQLite is a way to use the SQLite database entirely in memory. Instead of saving data to a <code>.db</code> file, SQLite creates the database only while the application or connection is active. When the connection is closed, all data is discarded.</p><p>In-memory SQLite is widely used in automated testing, especially in ASP.NET Core applications in conjunction with Entity Framework Core, as it has advanced features such as simulating a real database and executing real SQL (constraints, indexes, unique keys, FK and others).</p><h2 id="testing-with-sqlite-in-memory">Testing with SQLite in Memory</h2><p>Next, we&rsquo;ll look at some examples of unit tests in common web API scenarios where in-memory SQLite excels. But first, let&rsquo;s create the basic application and then add the tests.</p><p>You can access all the code discussed in this post in this GitHub repository: <a target="_blank" href="https://github.com/zangassis/memo-order">Memo Order source code</a>.</p><p>To create the application you can use the command below:</p><pre class=" language-bash"><code class="prism  language-bash">dotnet new web -o MemoOrder
</code></pre><p>To add the NuGet packages you can use the following commands:</p><pre class=" language-bash"><code class="prism  language-bash">dotnet add package Microsoft.EntityFrameworkCore --version 10.0.1
dotnet add package Microsoft.EntityFrameworkCore.Sqlite --version 10.0.1
</code></pre><p>Then, open the application and create the following classes:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">namespace</span> MemoOrder<span class="token punctuation">;</span>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">addPromptOutput</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
                id<span class="token punctuation">:</span> kendo<span class="token punctuation">.</span><span class="token function">guid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
                output<span class="token punctuation">:</span> <span class="token keyword">await</span> <span class="token function">putAgentPrompt</span><span class="token punctuation">(</span>newPrompt<span class="token punctuation">,</span> mode<span class="token punctuation">)</span><span class="token punctuation">,</span>
                prompt<span class="token punctuation">:</span> lastPrompt
             <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">activeView</span><span class="token punctuation">(</span><span class="token string">"output"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span><span class="token punctuation">,</span>    
</code></pre><p>The result looks like this, with the result of executing the menu choice added to the output from the user&rsquo;s previous requests:</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-03/aiprompt-javascript-5--commands-with-multiple-output.png?sfvrsn=71c263e3_2" alt="AIPrompt’s output view showing two results: At the top is a 200 word summary that results from executing the “Summarize” menu choice; below that is the output from an earlier prompt requesting the minimum code to execute " /></p><h2 id="providing-sample-prompts">Providing Sample Prompts</h2><p>One last thing: It&rsquo;s not unusual for an AI agent&rsquo;s UI to include sample prompts to help users see what they can do with your application. Progress AIPrompt will let you do that, too, just by adding the <code>promptSuggestions</code> property to the object passed to <code>kendoAIPrompt</code> and setting it to an array of strings. This example loads three suggested prompts to AIPrompt:</p><pre class=" language-javascript"><code class="prism  language-javascript"><span class="token function">$</span><span class="token punctuation">(</span><span class="token string">"#customAgent"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">kendoAIPrompt</span><span class="token punctuation">(</span>
    <span class="token punctuation">{</span>
        promptRequest<span class="token punctuation">:</span> <span class="token keyword">async</span> <span class="token keyword">function</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span> 
        <span class="token punctuation">{</span> 
           &hellip;previous code&hellip;
        <span class="token punctuation">}</span><span class="token punctuation">,</span>
        promptSuggestions<span class="token punctuation">:</span> 
       <span class="token punctuation">[</span>
         <span class="token string">"Summarize, targeting project leads"</span><span class="token punctuation">,</span>             
        <span class="token string">"What are the key features"</span><span class="token punctuation">,</span>
         <span class="token string">"What are the critical steps"</span>
     <span class="token punctuation">]</span><span class="token punctuation">,</span>
     <span class="token punctuation">{</span>
</code></pre><p>The result looks like this:</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-03/aiprompt-javascript-6--suggestions.png?sfvrsn=76c7a97_2" alt="The AIPrompt interface with the three suggested prompts defined in the sample code displayed in lozenge shaped buttons below the prompt textbox (and labeled Prompt Suggestion with an arrow that allows you to hide their display). The Generate button appears below the three suggestions" /></p><p>Now, when the user clicks on one of your suggested prompts, that prompt will be automatically copied into the prompt textbox where the user can modify the suggestion before clicking the Generate button. This can also simplify testing&mdash;instead of typing in a test prompt, you can just pick one of your suggestions.</p><p>Combining Telerik Document Processing Library and the library&rsquo;s AI Connectors lets you create your own custom AI Agent. And the AIPrompt, in any of its implementations, lets you create a front end that enables your users take advantage of your backend (and meets your users&rsquo; expectations). You&rsquo;re ready to give your users a whole new level of support.</p><hr /><p>Remember, you can get access to all of the Kendo UI and Telerik components in this series with a free 30-day trial of the <a target="_blank" href="https://www.telerik.com/devcraft">Telerik DevCraft</a> bundle.</p><p><a href="https://www.telerik.com/try/devcraft-ultimate" target="_blank" class="Btn">Try Now</a></p><img src="https://feeds.telerik.com/link/23068/17335600.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:2f40dbfd-768b-4958-af6a-1001a16c7243</id>
    <title type="text">Creating a Custom AI Agent with Telerik Tools 4: Crafting an Interactive Blazor UI</title>
    <summary type="text">If you’re creating an AI-enabled backend, your users expect an interface that supports working with an AI tool. The Telerik UI for Blazor AIPrompt wraps that all up into a single component.</summary>
    <published>2026-04-29T18:56:24Z</published>
    <updated>2026-10-22T21:58:28Z</updated>
    <author>
      <name>Peter Vogel </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23068/17335597/creating-custom-ai-agent-telerik-tools-4-crafting-interactive-blazor-ui"/>
    <content type="text"><![CDATA[<p><span class="featured">If you&rsquo;re creating an AI-enabled backend, your users expect an interface that supports working with an AI tool. The Telerik UI for Blazor AIPrompt wraps that all up into a single component.</span></p><p>If you&rsquo;re creating an AI-enabled application, you can, of course, create any frontend for that application that makes sense to your users. But that &ldquo;makes sense&rdquo; must take into account your user&rsquo;s expectations around the kind of user interface that an AI-enabled application should provide&mdash;expectations that are set by tools like OpenAI&rsquo;s <a target="_blank" href="https://chatgpt.com/">ChatGPT</a> client.</p><p>Typically, that means supporting an interactive flow that allows the user to evolve through a set of prompts to move from an initial response from your AI-enabled application to a response that better meets the user&rsquo;s needs. The Progress Telerik AIPrompt component creates that UI in a single component.</p><p>In previous posts, I walked through <a href="https://www.telerik.com/blogs/creating-custom-ai-agent-telerik-tools-1-configuring-llm-azure-ollama" target="_blank">configuring an LLM in Azure or Ollama</a>, <a href="https://www.telerik.com/blogs/loading-accessing-converting-office-pdf-documents-telerik-document-processing-libraries" target="_blank">creating content with Telerik Document Processing Library</a>, <a href="https://www.telerik.com/blogs/creating-custom-ai-agent-telerik-tools-2-loading-accessing-agent-content" target="_blank">tying that content to my LLM</a>&nbsp;and leveraging the <a href="https://www.telerik.com/blogs/creating-custom-ai-agent-telerik-tools-3-summarizing-querying" target="_blank">Telerik DPL AI connectors</a>.</p><p>For this post, I&rsquo;m going to create UI with Telerik <a target="_blank" href="https://www.telerik.com/blazor-ui/ai-prompt">AI Prompt for Blazor</a> to let users interact with my custom agent.</p><p>My next post will use the <a href="https://www.telerik.com/design-system/docs/components/aiprompt/" target="_blank">JavaScript for Kendo UI version</a>. And, while I won&rsquo;t be covering them, there are also versions of the AI Prompt component for <a target="_blank" href="https://www.telerik.com/products/aspnet-ajax/ai-prompt.aspx">ASP.NET AJAX</a>, <a target="_blank" href="https://www.telerik.com/products/winforms/aiprompt.aspx">WinForms</a> and <a target="_blank" href="https://www.telerik.com/maui-ui/aiprompt">.NET MAUI</a>).</p><h2 id="creating-the-initial-display">Creating the Initial Display</h2><p>To get started, you&rsquo;ll need to create a Telerik-enabled application (e.g., for <a target="_blank" href="https://www.telerik.com/blazor-ui/documentation/getting-started/web-app">Blazor</a>), adding the Telerik.UI.for.Blazor and Telerik.AI.SmartComponents.Extensions NuGet packages to your application (in addition to the AI and document processing packages I described in my previous posts).</p><p>With those in place, you can then add your AIPrompt to your user interface, which can be as simple as this:</p><pre class=" language-razor"><code class="prism  language-razor">&lt;TelerikAIPrompt OnPromptRequest="@HandlePromptRequest"&gt;
&lt;/TelerikAIPrompt&gt;
</code></pre><p>Your next step is to write the method in the <code>OnPromptRequest</code> that will be called whenever the user clicks the AIPrompt Generate button (I&rsquo;ll call this the &ldquo;prompt method&rdquo;).</p><p>Your prompt method will be passed an <code>AIPromptPromptRequestEventArgs</code> parameter whose Prompt property will contain whatever prompt the user has typed into the AIPrompt textbox.</p><p>All you have to do is call your custom agent (I&rsquo;ve assumed that&rsquo;s a class called <code>CustomAgent</code>), pass the user&rsquo;s prompt to whatever method it exposes (I&rsquo;ve assumed a method called <code>ProcessRequest</code>), and then update the prompt method parameter&rsquo;s <code>Output</code> property with the result of that processing.</p><p>That code could be as simple as this:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">private</span> <span class="token keyword">async</span> Task <span class="token function">HandlePromptRequest</span><span class="token punctuation">(</span>AIPromptPromptRequestEventArgs args<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
   <span class="token keyword">private</span> CustomAgent proc <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
   args<span class="token punctuation">.</span>Output <span class="token operator">=</span> <span class="token keyword">await</span> proc<span class="token punctuation">.</span><span class="token function">ProcessRequest</span><span class="token punctuation">(</span>args<span class="token punctuation">.</span>Prompt<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>And with that in place, your UI looks like this:</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-03/ai-ui-2-combined.png?sfvrsn=379bc4fe_2" alt="Two successive screenshots: First, the AIPrompt showing a textbox with a prompt entered into it (“what code do I need to use scrolltoitem”); Second, AIPrompt’s output showing a block of unformatted code" /></p><p>My CustomAgent object&rsquo;s ProcessRequest method does three things:</p><ol><li>Accesses a Large Language Model (LLM) and creates a chat client to work with it</li><li>Loads some content using tools from Progress <a target="_blank" href="https://www.telerik.com/document-processing-libraries">Telerik Document Processing Libraries (DPL)</a> to create the agent&rsquo;s content</li><li>Passes the chat client and content to the Telerik SummarizationProcessor AI connector and returns the result</li></ol><p>That code looks like this (I&rsquo;ve covered it in more detail in my earlier posts):</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token keyword">async</span> Task<span class="token operator">&lt;</span><span class="token keyword">string</span><span class="token operator">&gt;</span> <span class="token function">ProcessRequest</span><span class="token punctuation">(</span><span class="token keyword">string</span> prompt<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
      aiclt <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Uri</span><span class="token punctuation">(</span><span class="token string">"&lt;LLM deployment name&gt;"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
                             <span class="token keyword">new</span> <span class="token class-name">AzureKeyCredential</span><span class="token punctuation">(</span><span class="token string">"&lt;Access Key&gt;"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      chatClt <span class="token operator">=</span> aiclt<span class="token punctuation">.</span><span class="token function">GetChatClient</span><span class="token punctuation">(</span><span class="token string">"&lt;LLM deployment name&gt;"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">AsIChatClient</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

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

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

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

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

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

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

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

<span class="token keyword">private</span> <span class="token keyword">async</span> <span class="token keyword">void</span> <span class="token function">HandleCommandExecute</span><span class="token punctuation">(</span>AIPromptCommandExecuteEventArgs args<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
   <span class="token keyword">if</span> <span class="token punctuation">(</span>args<span class="token punctuation">.</span>Command<span class="token punctuation">.</span>Id<span class="token punctuation">.</span><span class="token function">StartsWith</span><span class="token punctuation">(</span> <span class="token string">"Summarize"</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
   <span class="token punctuation">{</span>
      proc<span class="token punctuation">.</span>Process <span class="token operator">=</span> <span class="token string">"Summarize"</span><span class="token punctuation">;</span>
      summarizeLimit <span class="token operator">=</span> args<span class="token punctuation">.</span>Command<span class="token punctuation">.</span>Prompt<span class="token punctuation">;</span>    
   <span class="token punctuation">}</span>
   <span class="token keyword">else</span>
   <span class="token punctuation">{</span>
      proc<span class="token punctuation">.</span>Process <span class="token operator">=</span> args<span class="token punctuation">.</span>Command<span class="token punctuation">.</span>Id<span class="token punctuation">;</span>
      summarizeLimit <span class="token operator">=</span> <span class="token keyword">string</span><span class="token punctuation">.</span>Empty<span class="token punctuation">;</span>   
   <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>I also need to update my prompt command to see if my <code>summarizeList</code> field has anything in it. If the field does, I&rsquo;ll add the field&rsquo;s text to whatever the user has entered as their prompt:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">private</span> <span class="token keyword">string</span> prevPrompt <span class="token operator">=</span> <span class="token keyword">string</span><span class="token punctuation">.</span>Empty<span class="token punctuation">;</span>
<span class="token keyword">private</span> <span class="token keyword">async</span> Task <span class="token function">HandlePromptRequest</span><span class="token punctuation">(</span>AIPromptPromptRequestEventArgs args<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
   <span class="token keyword">string</span> innertPrompt <span class="token operator">=</span> <span class="token keyword">string</span><span class="token punctuation">.</span>Empty<span class="token punctuation">;</span>
   <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token keyword">string</span><span class="token punctuation">.</span><span class="token function">IsNullOrEmpty</span><span class="token punctuation">(</span>summarizeLimit<span class="token punctuation">)</span><span class="token punctuation">)</span>
   <span class="token punctuation">{</span>
      prevPrompt <span class="token operator">=</span> args<span class="token punctuation">.</span>Prompt<span class="token punctuation">;</span>
      innerPrompt <span class="token operator">+</span><span class="token operator">=</span> summarizeLimit <span class="token operator">+</span> <span class="token string">", "</span> <span class="token operator">+</span> args<span class="token punctuation">.</span>Prompt
   <span class="token punctuation">}</span>
   <span class="token keyword">else</span>
   <span class="token punctuation">{</span>
      innerPrompt <span class="token operator">=</span> args<span class="token punctuation">.</span>Prompt<span class="token punctuation">;</span>
   <span class="token punctuation">}</span>
   args<span class="token punctuation">.</span>Output <span class="token operator">=</span> Markdown<span class="token punctuation">.</span><span class="token function">ToHtml</span><span class="token punctuation">(</span><span class="token keyword">await</span> proc<span class="token punctuation">.</span><span class="token function">ProcessDocument</span><span class="token punctuation">(</span>innerPrompt<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>As you can see in this code, I&rsquo;m also hanging onto the user&rsquo;s initial prompt whenever they&rsquo;re summarizing content. I&rsquo;m doing this so that, in the next section, I can demonstrate how to add your own custom output views to the AIPrompt output history.</p><h3 id="displaying-custom-output">Displaying Custom Output</h3><p>AIPrompt also lets me create custom output from anywhere in my code and add that output to the AIPrompt default output view.</p><p>I can, in my command method, call my AI processor. However, by default, output created in my command method won&rsquo;t update the AIPrompt default output view. What I can do in my command view, however, is create some custom output and add that to the AIPrompt default output view.</p><p>That can be useful because, if a user selects one of my summarize subcommands, my user might reasonably expect to see their previous prompt re-executed with the new summarize subcommand applied to it.</p><p>To do that, I first need to declare a field to reference my AIPrompt:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">private</span> TelerikAIPrompt<span class="token operator">?</span> aiprompt <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
</code></pre><p>And then tie that field to my AIPrompt:</p><pre class=" language-razor"><code class="prism  language-razor">&lt;TelerikAIPrompt @ref="aiprompt"
                                     OnPromptRequest="@HandlePromptRequest"
                                     &hellip;
</code></pre><p>Now, in my command method, I can call my processor, pass it my user&rsquo;s previous prompt (which I saved in my prompt method), apply whatever command the user selected and catch the new result:</p><pre class=" language-csharp"><code class="prism  language-csharp">proc<span class="token punctuation">.</span>Process <span class="token operator">=</span> <span class="token string">"Summarize"</span><span class="token punctuation">;</span>
summarizeLimit <span class="token operator">=</span> args<span class="token punctuation">.</span>Command<span class="token punctuation">.</span>Prompt<span class="token punctuation">;</span>
<span class="token keyword">string</span> result <span class="token operator">=</span> <span class="token keyword">await</span> proc<span class="token punctuation">.</span><span class="token function">ProcessDocument</span><span class="token punctuation">(</span>summarizeLimit <span class="token operator">+</span> <span class="token string">", "</span> <span class="token operator">+</span> prevPrompt<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>With that new result in hand, I can use my reference to the <code>TelerikAIPrompt</code> component to call the AIPrompt <code>AddOutput</code> method, passing the parameters to generate a new AIPrompt output view.</p><p>The <code>AddOutput</code> method accepts up to six parameters, but the primary ones are:</p><ul><li><code>output</code>: The result of your processing</li><li><code>title</code>: The large text displayed above your result</li><li><code>subtitle</code>: Some smaller text displayed below the title</li><li><code>prompt</code>: The prompt used to generate the result</li></ul><p>After adding my custom output, I then also call the AIPrompt <code>Refresh</code> method to have AIPrompt update its output view with my new result.</p><p>Altogether, the code looks like this:</p><pre class=" language-csharp"><code class="prism  language-csharp">aiprompt<span class="token punctuation">.</span><span class="token function">AddOutput</span><span class="token punctuation">(</span>
output<span class="token punctuation">:</span> result<span class="token punctuation">,</span>
title<span class="token punctuation">:</span> <span class="token string">"Summarize: "</span> <span class="token operator">+</span> args<span class="token punctuation">.</span>Command<span class="token punctuation">.</span>Prompt<span class="token punctuation">,</span>
subtitle<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">.</span>Empty<span class="token punctuation">,</span>
prompt<span class="token punctuation">:</span> prevPrompt<span class="token punctuation">,</span>
commandId<span class="token punctuation">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span>
openOutputView<span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
aiprompt<span class="token punctuation">.</span><span class="token function">Refresh</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-03/ai-ui-7-terse-and-verbose.png?sfvrsn=bf2acb15_2" alt="A screenshot of a history of output in AIPrompt. All of the items have the same prompt: “when can’t I use scrolltoitem.” The top output block is labelled Terse and consists of a single sentence less than 20 words long; the block below it is labelled Verbose and is a paragraph of less than 100 words" /></p><h2 id="providing-suggested-prompts">Providing Suggested Prompts</h2><p>One last thing: It&rsquo;s possible that your users may not realize the variety of prompts they can provide to your application. Alternatively, you might want to guide users <em>away</em> from entering some prompts by providing your users with some &ldquo;approved prompts.&rdquo; To support either of those goals, AIPrompt lets you provide a list of suggested prompts.</p><p>First, you need to create an array of strings with some suggested prompts:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">private</span> List<span class="token operator">&lt;</span><span class="token keyword">string</span><span class="token operator">&gt;</span> Suggestions <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> 
    <span class="token keyword">new</span> <span class="token class-name">List</span><span class="token operator">&lt;</span><span class="token keyword">string</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
   <span class="token punctuation">{</span>
      <span class="token string">"Summarize in under 100 words, targeting developers"</span><span class="token punctuation">,</span>
      <span class="token string">"Summarize in under 100 words, targeting project leads"</span><span class="token punctuation">,</span>
      <span class="token string">"What are the key features"</span><span class="token punctuation">,</span>
      <span class="token string">"What are the critical steps"</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>To integrate that list of suggested prompts, you just need to set the <code>TelerikAIPrompt</code> component&rsquo;s <code>PromptSuggestions</code> property to the name of your array of strings:</p><pre class=" language-razor"><code class="prism  language-razor">&lt;TelerikAIPrompt @ref="aiprompt"
 OnPromptRequest="@HandlePromptRequest"
 PromptSuggestions="@Suggestions"
&hellip;
</code></pre><p>The result looks like this:</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-03/ai-ui-8-suggestions.png?sfvrsn=14decfb2_2" alt="The AIPrompt from before but it’s bottom has been filled with a set of lozenge shaped buttons with the strings from the Suggestions array in the component (e.g. What are the key features, Summarize this document for…" /></p><p>The user can then click on any of these suggestions to add them to the AIPrompt textbox where the user can then edit or modify the suggestion before submitting it as their next prompt. This can also simplify testing&mdash;instead of typing in a test prompt, you can just pick one of your suggestions.</p><p>You now have all the tools you need to create an application that lets your users leverage your custom AI agent to provide a quick source of information from any content you might want to provide &hellip; in Blazor, at least.</p><p><a href="https://www.telerik.com/blogs/creating-custom-ai-agent-telerik-tools-5-creating-interactive-ui-javascript" target="_blank">In my next post, I&rsquo;ll move my processor into a web service and access it from the JavaScript version of the AIPrompt component.</a></p><hr /><p>Remember, you can get access to all of the Kendo UI and Telerik components in this series with a free 30-day trial of the <a target="_blank" href="https://www.telerik.com/devcraft">Telerik DevCraft</a> bundle.</p><p><a href="https://www.telerik.com/try/devcraft-ultimate" target="_blank" class="Btn">Try Now</a></p><img src="https://feeds.telerik.com/link/23068/17335597.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:ef8e4643-5bc4-4e34-9174-357e1d25de81</id>
    <title type="text">Creating a Custom AI Agent with Telerik Tools 3: Summarizing and Querying</title>
    <summary type="text">Now we’ll add content to the LLM using AI processors from the Progress Telerik Document Processor Libraries to summarize and query our agent’s content.</summary>
    <published>2026-04-22T17:06:35Z</published>
    <updated>2026-10-22T21:58:28Z</updated>
    <author>
      <name>Peter Vogel </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23068/17323733/creating-custom-ai-agent-telerik-tools-3-summarizing-querying"/>
    <content type="text"><![CDATA[<p><span class="featured">Now we&rsquo;ll add content to the LLM using AI processors from the Progress Telerik Document Processor Libraries to summarize and query our agent&rsquo;s content.</span></p><p>In this series of posts, I&rsquo;ve been creating a custom AI Agent using Telerik tools. That&rsquo;s included <a target="_blank" href="https://www.telerik.com/blogs/creating-custom-ai-agent-telerik-tools-1-configuring-llm-azure-ollama">accessing an Azure Large Language Model</a> (LLM) and loading <a target="_blank" href="https://www.telerik.com/blogs/creating-custom-ai-agent-telerik-tools-2-loading-accessing-agent-content">my own content and creating a client to access the LLM</a>.</p><p>The last step is to pass my content to my LLM using one of the Progress Telerik AI processors (part of the <a target="_blank" href="https://docs.telerik.com/devtools/document-processing/introduction">Document Processor Libraries</a>), which is what this post is about.</p><p>The <a target="_blank" href="https://docs.telerik.com/devtools/document-processing/libraries/radwordsprocessing/editing/gen-ai-powered-document-insights/overview">Telerik AI processors</a> support two scenarios for your users: summarizing your agent&rsquo;s content and querying your agent&rsquo;s content.</p><p>To summarize your content, you&rsquo;ll use the Telerik <a target="_blank" href="https://docs.telerik.com/devtools/document-processing/libraries/radwordsprocessing/editing/gen-ai-powered-document-insights/summarization-processor"><code>SummarizationProcess</code></a> processor. For querying content, on the other hand, you have a choice between two processors:</p><ul><li><a target="_blank" href="https://docs.telerik.com/devtools/document-processing/libraries/radwordsprocessing/editing/gen-ai-powered-document-insights/complete-context-question-processor"><code>CompleteContextQuestionProcessor</code></a> which loads all of your content before querying it</li><li><a target="_blank" href="https://docs.telerik.com/devtools/document-processing/libraries/radwordsprocessing/editing/gen-ai-powered-document-insights/partial-context-question-processor"><code>PartialContentQuestionProcessor</code></a> which loads just part of your content as a series of fragments&mdash;a good choice when your content is large and you don&rsquo;t want to allocate (i.e., &ldquo;pay for&rdquo;) enough tokens to load all of it at once</li></ul><p>A token, by the way, represents a word, part of a word or a punctuation mark. The sample document I&rsquo;ll be using to demonstrate the summarization process contains roughly 1,500 words (a relatively small document). So, when summarizing that document, I&rsquo;ll set my token count to 3,500 (I figured doubling the word count to include punctuation marks and adding a 33% buffer would work).</p><p>On the other hand, to demonstrate querying, I&rsquo;ll be loading three documents as my agent&rsquo;s content, totaling about 8,000 words. That&rsquo;s going to require either a larger token count or fragmenting my document.</p><h2 id="summarizing-documents">Summarizing Documents</h2><p>Before you can do any summarizing or querying, you&rsquo;ll need to add the Telerik.Documents.AIConnector NuGet package to your project. With that package added, to summarize your agent&rsquo;s content you&rsquo;ll use the Telerik <code>SummarizationProcessor</code> processor. Your first step is to create a settings object the model that defines the context window for the model by specifying:</p><ul><li>The maximum of number of tokens you&rsquo;re willing to have used in processing the document</li><li>Any text describing how you want to customize the summarization process (e.g., &ldquo;under 100 words,&rdquo; &ldquo;target project managers&rdquo;)</li></ul><p>Once you&rsquo;ve created the settings object, you create a <code>SummarizationProcessor</code> object, passing a chat client (discussed in my last post) and your settings object. Once you&rsquo;ve created the processor, you pass its <code>Summarize</code> method your content and catch the result as a string.</p><p>In the following code, I&rsquo;ve assumed that you&rsquo;ve used Telerik Document Processing Libraries to load a <code>SimpleTextDocument</code> into a variable called <code>std</code> (again, see my previous posts). The code then:</p><ol><li>Creates a chat client tied to an LLM deployed in Azure</li><li>Creates a settings object that tells the summarization processor to
        <ul><li>Accept up to 3,500 tokens</li><li>Asks the processor to summarize the document in less than 100 words</li></ul></li><li>Creates the <code>SummarizationProcessor</code> from the chat client and the settings object</li><li>Passes my content document to the <code>SummarizationProcessor</code>&rsquo;s <code>Summarize</code> method</li><li>Catches the result as a string</li></ol><p>And it&rsquo;s only four lines of code:</p><pre class=" language-csharp"><code class="prism  language-csharp">AzureOpenAIClient aiclt <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span>
                <span class="token keyword">new</span> <span class="token class-name">Uri</span><span class="token punctuation">(</span><span class="token string">"&lt;deployment URL&gt;"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
                <span class="token keyword">new</span> <span class="token class-name">AzureKeyCredential</span><span class="token punctuation">(</span><span class="token string">"&lt;access key&gt;"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
IChatClient chatClt <span class="token operator">=</span> aiclt<span class="token punctuation">.</span><span class="token function">GetChatClient</span><span class="token punctuation">(</span><span class="token string">"&lt;Deployment Name&gt;"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">AsIChatClient</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            <span class="token punctuation">[</span>System<span class="token punctuation">.</span>Text<span class="token punctuation">.</span>Json<span class="token punctuation">.</span>Serialization<span class="token punctuation">.</span><span class="token function">JsonPropertyName</span><span class="token punctuation">(</span><span class="token string">"index"</span><span class="token punctuation">)</span><span class="token punctuation">]</span>
            <span class="token keyword">public</span> <span class="token keyword">int</span> Index <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>   
</code></pre><img src="https://feeds.telerik.com/link/23068/17323733.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:6cffd8be-e8a5-4991-8d63-3944f9ce8737</id>
    <title type="text">When Your Coding Assistant Finally Got X-Ray Vision</title>
    <summary type="text">The Fiddler Everywhere MCP server gives your coding assistant network inspection capabilities for debugging right inside your editor.</summary>
    <published>2026-04-20T16:14:55Z</published>
    <updated>2026-10-22T21:58:28Z</updated>
    <author>
      <name>Nikolay Iliev </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23068/17322386/when-your-coding-assistant-finally-got-x-ray-vision"/>
    <content type="text"><![CDATA[<p><span class="featured">The Fiddler Everywhere MCP server gives your coding assistant network inspection capabilities for debugging right inside your editor.</span></p><p>Let me paint you a picture.</p><p>You are sitting at your desk. Your app loads in the browser. The page renders. The buttons work. The data shows up. Everything looks fine.</p><p>Except it is not fine. Somewhere underneath that perfectly rendered page, a request is taking 3 seconds when it should take 200 milliseconds. Another endpoint is returning a 200 but leaking an auth token in a query parameter. A third one is completely missing its Cache-Control header, so every single page refresh costs your users the full round-trip all over again.</p><p>But hey&mdash;the page loads. So it is fine, right?</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-04/this-is-fine.png?sfvrsn=1250927d_2" alt="Question Hound comic meme with dog sitting in room on fire saying, This is fine" /><br /><span style="font-size:11px;">Image Credit: <a target="_blank" href="https://gunshowcomic.com/648">KC Green</a></span><br /><span style="font-size:14px;"><strong>Your browser: &ldquo;Everything rendered successfully!&rdquo; Your network traffic: literal dumpster fire.</strong></span></p><p>This was the old debugging workflow for years: You open DevTools. You scroll through 47 network requests. You squint at the Timing column. You click into a request, copy the headers, paste them into a notepad, try to figure out if X-Content-Type-Options is set or not. By request number six, you have forgotten what you found in request number two. You do that for 12 more requests.</p><p>Then you switch back to your editor, ask your coding assistant for help, and it says something like: &ldquo;You might want to check if your caching headers are configured properly.&rdquo;</p><p>Might. Want. To check.</p><p>It does not know. It cannot know. It is reading your Express routes and giving you its best guess based on what the code <em>probably</em> does. It has never seen the actual traffic.</p><h2 id="now-imagine-this-instead">Now Imagine This Instead</h2><p>You open your editor. You type:</p><p><code>#fiddler Start capturing HTTP traffic in Fiddler using Chrome</code></p><p>A Chrome window opens with the Fiddler proxy already configured. You click around your app for thirty seconds. Then you type:</p><p><code>#fiddler Identify the slowest API endpoints in the captured traffic</code></p><p>And your coding assistant&mdash;which can now <em>actually see</em> the captured sessions&mdash;comes back with:</p><p><code>GET /api/products/featured &mdash; average 2,847ms across 4 requests, 52KB response body, no Cache-Control header. This is your primary bottleneck.</code></p><p>No guessing. No &ldquo;you might want to.&rdquo; Just facts, pulled from real HTTPS sessions.</p><p>This is what the Progress Telerik <a target="_blank" href="https://www.telerik.com/fiddler/fiddler-everywhere/mcp">Fiddler Everywhere MCP server</a> does. It gives your coding assistant network vision. MCP&mdash;Model Context Protocol&mdash;is the open standard that lets your editor&rsquo;s assistant call external tools during a conversation. The Fiddler MCP server exposes captured traffic data through those tools: session lists, request and response details, headers, bodies, timing, TLS info&mdash;the whole picture.</p><p>The setup? A JSON config file in your workspace:</p><pre class=" language-json"><code class="prism  language-json"><span class="token punctuation">{</span>  
  <span class="token string">"servers"</span><span class="token punctuation">:</span> <span class="token punctuation">{</span>  
    <span class="token string">"fiddler"</span><span class="token punctuation">:</span> <span class="token punctuation">{</span>  
      <span class="token string">"type"</span><span class="token punctuation">:</span> <span class="token string">"http"</span><span class="token punctuation">,</span>  
      <span class="token string">"url"</span><span class="token punctuation">:</span> <span class="token string">"http://localhost:8868/mcp"</span><span class="token punctuation">,</span>  
      <span class="token string">"headers"</span><span class="token punctuation">:</span> <span class="token punctuation">{</span>  
        <span class="token string">"Authorization"</span><span class="token punctuation">:</span> <span class="token string">"ApiKey YOUR_API_KEY_HERE"</span>  
      <span class="token punctuation">}</span>  
    <span class="token punctuation">}</span>  
  <span class="token punctuation">}</span>  
<span class="token punctuation">}</span>  
</code></pre><p>Generate the key in Fiddler Everywhere, drop the config and you are connected. Works with VS Code, Cursor, Claude Code, Windsurf, Copilot CLI&mdash;the full lineup.</p><h2 id="the-prompts-that-make-you-feel-like-a-wizard">The Prompts That Make You Feel Like a Wizard</h2><p>Once Fiddler MCP is connected, the things you can ask your assistant become genuinely fun:</p><ul><li><code>#fiddler Show me all failed requests (status codes 4xx and 5xx)</code></li><li><code>#fiddler Identify sessions with weak or missing security headers</code></li><li><code>#fiddler Create a comprehensive report covering security highlights and performance optimizations</code></li><li><code>#fiddler Analyze caching efficiency for the captured sessions</code></li></ul><p>Each of these triggers real tool calls. The assistant reads actual captured sessions, not your imagination of what the sessions might look like. That means the security audit it produces is based on the headers your server <em>actually sent</em>, not the headers it <em>assumes</em> you configured.</p><p>The <a target="_blank" href="https://www.telerik.com/fiddler/fiddler-everywhere/documentation/mcp-server/prompt-library">Fiddler Prompt Library</a> has dozens more if you want to explore.</p><h2 id="skip-the-setup-fiddler-agent-skills">Skip the Setup: Fiddler Agent Skills</h2><p>If even the two-minute config feels like too much (I get it, we are all busy), the official <a target="_blank" href="https://github.com/telerik/fiddler-agent-tools">Fiddler agent skills</a> do it for you. Clone the repo, drop the skills into your project, and tell your assistant:</p><p><code>Set up Fiddler MCP</code></p><p>Done. Port discovery, API key retrieval, config file, .gitignore update&mdash;all handled. There is also a skill that analyzes captured traffic after you run a feature and gives you a structured pass/fail report. Honestly, it is the kind of thing that makes you wonder why you were ever doing it manually.</p><h2 id="build-your-own-debugging-agents-this-is-where-it-gets-good">Build Your Own Debugging Agents (This Is Where It Gets Good)</h2><p>Here is where things get spicy. The built-in skills are great, but you can write your own. A skill is just a Markdown file&mdash;a <a target="_blank" href="http://SKILL.md">SKILL.md</a>&mdash;that tells your assistant which Fiddler tools to call, in what order, and how to format the output.</p><p>Some agents I think every team should consider building:</p><ul><li><p><strong>traffic-security-auditor</strong> &ndash; Scans every session for missing security headers, tokens exposed in URLs, insecure connections. Produces a prioritized report. Basically your own automated pen-test lite that runs from a chat prompt.</p></li><li><p><strong>api-performance-watchdog</strong> &ndash; Flags slow endpoints, checks caching headers, reports payload bloat. Think of it as a performance review for your API&mdash;except this one is actually useful. (Sorry, managers.)</p></li><li><p><strong>checkout-flow-verifier</strong> &ndash; Replays a captured e-commerce checkout flow and verifies each step hit the right status codes and redirects. Because nobody wants to find out the payment confirmation page is broken from an angry customer email.</p></li></ul><p>The <a target="_blank" href="https://www.telerik.com/fiddler/fiddler-everywhere/documentation/agent-tools/creating-custom-skills">Creating Custom Skills</a> guide has the full format and a working example.</p><h2 id="you-never-have-to-leave-your-editor">You Never Have to Leave Your Editor</h2><p>Here is the part that really lands once you try it: you do not need to switch to Fiddler Everywhere. You do not need to open a separate desktop application. You do not need to context-switch at all.</p><p>Everything happens inside the tool you are already using&mdash;VS Code with Copilot, Claude Code in the terminal, Cursor, Windsurf, whatever your setup is. Capture traffic, filter sessions, inspect headers, create rules, run a full security audit, all from the same chat window where you write code.</p><p>That is the whole point. The MCP server brings Fiddler network inspection capabilities directly into your coding workflow. Filters? Ask for them in a prompt. Rules? Create them with a sentence. Session details? One tool call away. You get the full power of a professional traffic inspector without ever breaking your flow.</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-04/enlightened-debugging.png?sfvrsn=6e4e7ed3_2" alt="Enlightenment meme with progressive stages: Reading server logs, opening Chrome DevTools, Opening Fiddler desktop, Typing #fiddler Analyze my traffic" /><br /><span style="font-size:14px;"><strong>When your entire debugging workflow fits inside one chat window and zero browser tabs.</strong></span></p><h2 id="try-it">Try It</h2><p>Seriously&mdash;if you are already using a coding assistant while you code, giving it access to your network traffic is one of the highest-leverage things you can do. The guessing stops. The analysis gets real. And you might actually enjoy debugging for once.</p><p><a href="https://www.telerik.com/download/fiddler-everywhere" class="Btn" target="_blank">Try Fiddler Everywhere</a></p><h3 id="seriously-though-tell-us-what-you-think">Seriously Though, Tell Us What You Think</h3><p>We are building this as fast as we can, and your feedback is what steers the ship. Tried a prompt that did not work well? Built a custom skill that saved your team hours? Found a bug? We want all of it.</p><ul><li><strong>Email</strong>: <a href="https://www.telerik.commailto:fiddler-support@progress.com" target="_blank">fiddler-support@progress.com</a></li><li><strong>GitHub issues</strong>: <a target="_blank" href="https://github.com/telerik/fiddler-everywhere/issues">github.com/telerik/fiddler-everywhere/issues</a></li></ul><img src="https://feeds.telerik.com/link/23068/17322386.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:9968b104-0478-45a0-bd7b-e2e5551dc52a</id>
    <title type="text">Creating a Custom AI Agent with Telerik Tools 2: Loading and Accessing Your Agent’s Content</title>
    <summary type="text">With your LLM set up with a custom AI agent, let’s connect our content via Telerik Document Processing Libraries AI processor—resulting in a RAG-enhanced app!</summary>
    <published>2026-04-15T20:54:34Z</published>
    <updated>2026-10-22T21:58:28Z</updated>
    <author>
      <name>Peter Vogel </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23068/17319853/creating-custom-ai-agent-telerik-tools-2-loading-accessing-agent-content"/>
    <content type="text"><![CDATA[<p>&nbsp;</p><p><span class="featured">With your LLM set up with a custom AI agent, let&rsquo;s connect our content via Telerik Document Processing Libraries AI processor&mdash;resulting in a RAG-enhanced app!</span></p><p>In my previous post, I covered how to set up a <a href="https://www.telerik.com/blogs/creating-custom-ai-agent-telerik-tools-1-configuring-llm-azure-ollama" target="_blank">Large Language Model (LLM) either in the cloud using Azure AI or on your desktop/server using Ollama</a>. This post walks through the code you need to load your own content into a custom AI agent based on that LLM.</p><p>That will let you create a <a target="_blank" href="https://www.telerik.com/blogs/fixing-llms-retrieval-augmented-generation">Retrieval-Augmented Generation</a>&ndash;enhanced application that provides grounded answers on the content that matters to your users. You could use this code to, for example, create a custom AI assistant for any part of your organization.</p><p>There are four steps to creating that application:</p><ol><li>Configure your application</li><li>Create a chat client to work with your LLM</li><li>Convert your content into a <a target="_blank" href="https://www.telerik.com/blogs/loading-accessing-converting-office-pdf-documents-telerik-document-processing-libraries">format that your chat client will accept using Progress Telerik Document Processing Libraries</a></li><li>Pass the chat client, your content and a prompt to the appropriate Telerik AI processor</li></ol><h2 id="configuring-the-application">Configuring the Application</h2><p>I&rsquo;m going to demonstrate the code for loading your content using a Blazor application (Blazor simplifies creating an interactive application that integrates server and client-side processing). However, the code in this post will be very similar in any C# application and, in a later post, I&rsquo;ll wrap my agent in a web service and access it from client-side JavaScript code.</p><p>Once your project is created, your next step is to add the necessary NuGet packages. The best advice I can give you around picking the right NuGet package is to a) include prerelease versions and b) always take the most recent package available.</p><p>The packages you&rsquo;ll need are:</p><ul><li>For OpenAI LLMs (like the LLMs I picked in my previous post):
        <ul><li>Azure.AI.OpenAI</li><li>Microsoft.Extensions.AI.OpenAI</li><li>OpenAI (probably already installed with the previous packages)</li></ul></li><li>For Ollama:
        <ul><li>OllamaSharp</li></ul></li><li>For Telerik document processing:
        <ul><li>Telerik.Documents.AIConnector</li><li>Telerik.Documents.Core</li><li>Telerik.Documents.Flow (to work with DOCX, HTML, and RTF files)</li><li>Telerik.Documents.Fixed (to work with PDF files)</li></ul></li></ul><h2 id="accessing-the-llm">Accessing the LLM</h2><p>Connecting to your LLM depends on whether you&rsquo;re using Azure or Ollama to host your LLM. (And, if you just created your LLM deployment, it can take up to 15 minutes before your LLM is ready to be used.)</p><h3 id="azure-hosted-llm">Azure-hosted LLM</h3><p>If you&rsquo;re using an Azure LLM then you need to create an <code>AzureOpenAIClient</code>, passing the URL and key from your deployment&rsquo;s information page in the <a target="_blank" href="http://ai.azure.com">ai.azure.com</a> portal (see my previous post).</p><p>Once you&rsquo;ve created your <code>AzureOpenAIClient</code>, you can use its <code>GetChatClient</code> method to retrieve a <code>ChatClient</code>. But, rather than access that <code>ChatClient</code> directly, you should use the <code>AsIChatClient</code> method to, effectively, cast the <code>ChatClient</code> to the more general purpose <code>IChatClient</code> interface. All that just requires just these two lines of code:</p><pre class=" language-csharp"><code class="prism  language-csharp">AzureOpenAIClient aiclt <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span>
                <span class="token keyword">new</span> <span class="token class-name">Uri</span><span class="token punctuation">(</span><span class="token string">"&lt;deployment URL&gt;"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
                <span class="token keyword">new</span> <span class="token class-name">AzureKeyCredential</span><span class="token punctuation">(</span><span class="token string">"&lt;access key&gt;"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
IChatClient chatClt <span class="token operator">=</span> aiclt<span class="token punctuation">.</span><span class="token function">GetChatClient</span><span class="token punctuation">(</span><span class="token string">"&lt;Deployment Name&gt;"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">AsIChatClient</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><h3 id="ollama-hosted-llm">Ollama-hosted LLM</h3><p>If you&rsquo;re working with Ollama, you create a chat client with the <code>IChatClient</code> interface by creating an <code>OllamaApiClient</code> object, passing the URL for your local Ollama server and the LLM that you want to use. That looks like this:</p><pre class=" language-csharp"><code class="prism  language-csharp">IChatClient chatClt <span class="token operator">=</span>
    <span class="token keyword">new</span> <span class="token class-name">OllamaApiClient</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Uri</span><span class="token punctuation">(</span><span class="token string">"&lt;address for Ollama&gt;"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> 
                                                   <span class="token string">"&lt;LLM name&gt;"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>Do be aware: For a typical development machine, processing documents using Ollama is <em>not</em> going to be as responsive as using an LLM on Azure. For example, the document I used in this case study contains about 1,500 words and took a few seconds to summarize using one of the Azure LLMs. Using Ollama, that process sometimes took over a minute. In some cases, your application may time out waiting for Ollama to respond.</p><p>You can deal with that issue in Ollama by creating a custom<code>HttpClient</code> object and passing it to your <code>OllamaApiClient</code> when you create it.</p><p>Here&rsquo;s some sample code that creates an <code>HttpClient</code> that is a) tied to the Ollama client&rsquo;s URL (probably http://localhost:11434) and b) sets a five-minute timeout. The code then uses that custom HttpClient to create an Ollama client:</p><pre class=" language-csharp"><code class="prism  language-csharp">HttpClient httpClt <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    BaseAddress <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Uri</span><span class="token punctuation">(</span><span class="token string">"&lt;address for Ollama&gt;"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    Timeout <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">TimeSpan</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">5</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 punctuation">;</span>

IChatClient chatClt <span class="token operator">=</span>
          <span class="token keyword">new</span> <span class="token class-name">OllamaApiClient</span><span class="token punctuation">(</span>httpClt<span class="token punctuation">,</span> <span class="token string">"&lt;model name&gt;"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><h2 id="loading-content">Loading Content</h2><p>The Telerik Document Processing Libraries (DPL) provide&nbsp;<a target="_blank" href="https://docs.telerik.com/devtools/document-processing/libraries/radwordsprocessing/editing/gen-ai-powered-document-insights/overview">multiple AI processors</a> for analyzing documents.</p><p>In this post, I&rsquo;m going to focus on the summarization processor (I&rsquo;ll look at other processors in my next post). Since all the processors expect to be passed a DPL <code>SimpleTextDocument</code>, switching between processors is simple.</p><p>As an example, here&rsquo;s the code to convert a DOCX file into a <code>SimpleTextDocument</code> using Telerik WordsProcessing library (the Flow library also handles RTF and HTML files; for PDF files, you would use Telerik PdfProcessing library):</p><pre class=" language-csharp"><code class="prism  language-csharp">RadFlowDocument dDoc<span class="token punctuation">;</span>
DocxFormatProvider dProv <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">using</span> <span class="token punctuation">(</span>Stream str <span class="token operator">=</span> System<span class="token punctuation">.</span>IO<span class="token punctuation">.</span>File<span class="token punctuation">.</span><span class="token function">OpenRead</span><span class="token punctuation">(</span><span class="token string">@"wwwroot/documents/scrolltoitem.docx"</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
            <span class="token punctuation">{</span>
                dDoc <span class="token operator">=</span> dProv<span class="token punctuation">.</span><span class="token function">Import</span><span class="token punctuation">(</span>str<span class="token punctuation">,</span> TimeSpan<span class="token punctuation">.</span><span class="token function">FromSeconds</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span>
SimpleTextDocument std <span class="token operator">=</span> dDoc<span class="token punctuation">.</span><span class="token function">ToSimpleTextDocument</span><span class="token punctuation">(</span>TimeSpan<span class="token punctuation">.</span><span class="token function">FromSeconds</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>Now that you have a <code>SimpleTextDocument</code>, your next step is to configure one of the Telerik AI processors to work with it.</p><p>In terms of AI processing, this document represents the content (or corpus) to be used by your LLM &hellip; but it&rsquo;s a very small corpus that consists of only a single document. While I&rsquo;m going to stick with a single document for this case study, you can use the <code>Merge</code> method on both Flow and Fixed documents to load multiple documents into a single document object before creating your <code>SimpleTextDocument</code> from that single document.</p><p>This code, for example, loads one DOCX file and then merges a second one into it, before creating a <code>SimpleTextDocument</code> from the result:</p><pre class=" language-csharp"><code class="prism  language-csharp">RadFlowDocument ddocMaster<span class="token punctuation">;</span>
RadFlowDocument ddocTemp<span class="token punctuation">;</span>
DocxFormatProvider dprov <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">using</span> <span class="token punctuation">(</span>Stream str <span class="token operator">=</span> System<span class="token punctuation">.</span>IO<span class="token punctuation">.</span>File<span class="token punctuation">.</span><span class="token function">OpenRead</span><span class="token punctuation">(</span><span class="token string">@"wwwroot/documents/InitialDoc.docx"</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    ddocMaster <span class="token operator">=</span> dprov<span class="token punctuation">.</span><span class="token function">Import</span><span class="token punctuation">(</span>str<span class="token punctuation">,</span> TimeSpan<span class="token punctuation">.</span><span class="token function">FromSeconds</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">using</span> <span class="token punctuation">(</span>Stream str <span class="token operator">=</span> System<span class="token punctuation">.</span>IO<span class="token punctuation">.</span>File<span class="token punctuation">.</span><span class="token function">OpenRead</span><span class="token punctuation">(</span><span class="token string">@"wwwroot/documents/SecondDoc.docx"</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    ddocTemp <span class="token operator">=</span> dprov<span class="token punctuation">.</span><span class="token function">Import</span><span class="token punctuation">(</span>str<span class="token punctuation">,</span> TimeSpan<span class="token punctuation">.</span><span class="token function">FromSeconds</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
ddocMaster<span class="token punctuation">.</span><span class="token function">Merge</span><span class="token punctuation">(</span>ddocTemp<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><h2 id="generating-summaries-in-your-agent">Generating Summaries in Your Agent</h2><p>The next step is to use one of the Telerik AI Connectors to enable your content for use by the LLM. To support this processing, you&rsquo;ll need to add the Telerik.Documents.AIConnector NuGet package to your project.</p><p>In this example, I&rsquo;m using the Telerik summarization processor to generate a summary of my custom content (more on the summarization processor and the other two Telerik AI processors in my next post):</p><pre class=" language-csharp"><code class="prism  language-csharp">SummarizationProcessorSettings spOpts <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token punctuation">(</span><span class="token number">3500</span><span class="token punctuation">,</span> <span class="token string">"Summarize in 100 words"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">using</span> <span class="token punctuation">(</span>SummarizationProcessor sp <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token punctuation">(</span>chatClt<span class="token punctuation">,</span> spOpts<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
   <span class="token keyword">string</span> summary <span class="token operator">=</span> <span class="token keyword">await</span> sp<span class="token punctuation">.</span><span class="token function">Summarize</span><span class="token punctuation">(</span>std<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>Don&rsquo;t expect a fast response when testing&mdash;it takes some time to absorb a complete document. The Azure-based LLMs I used would pause for three or four seconds to absorb each document, while the Ollama LLM took about 90 seconds on my laptop.</p><p>Which raises an important point: The summarization processor passes the <em>whole</em> document to your LLM&mdash;that can be both time consuming and expensive. You might want to catch the <a target="_blank" href="https://www.telerik.com/document-processing-libraries/documentation/libraries/radwordsprocessing/editing/gen-ai-powered-document-insights/summarization-processor"><code>SummaryResourceCalculated</code> event</a> that the processor raises. The <code>EventArgs</code> parameter passed to that event includes two properties (<code>EstimatedCallsRequired</code> and <code>EstimatedTokensRequired</code>) that you can check to see if the request is larger than you want to handle. If the request is &ldquo;too big,&rdquo; you can set the <code>EventArgs</code> parameter&rsquo;s <code>ShouldContinueExecution</code>property to false to stop processing.</p><p>And there you have your own, custom AI agent which you can load with whatever content you want to create. You can do more than just summarize document content, though, as I&rsquo;ll cover in my next post.</p><p>But, looking ahead to providing a frontend for users to access my custom AI agent, I have two UI issues that I should be thinking about to create a genuinely useful agent:</p><ul><li>Users will probably want to be able to enter an initial prompt and then refine it to get a better version of my custom agent&rsquo;s response.</li><li>Telerik has two other AI processors that support other types of AI processing. I want to give my user the chance to pick one of those other processors.</li></ul><p>And that&rsquo;s ignoring the reality that, thanks to the existing AI-enabled UIs my users are already using (looking at you, ChatGPT), my users have expectations about what an AI-enabled UI should look like. All that suggests that my application will need an interactive UI. So, after my <a href="https://www.telerik.com/blogs/creating-custom-ai-agent-telerik-tools-3-summarizing-querying" target="_blank">next post</a>, I&rsquo;m going to use the Telerik AI Prompt component to create a UI that provides my user with that interactivity.</p><hr /><p>Explore Telerik Document Processing Libraries, plus component libraries, reporting and more with a free trial of the Telerik DevCraft bundle:</p><p><a href="https://www.telerik.com/download" class="Btn" target="_blank">Try DevCraft</a></p><p>&nbsp;</p><img src="https://feeds.telerik.com/link/23068/17319853.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:1f05ab79-badb-485a-b3f4-0c01865d5b9a</id>
    <title type="text">Creating a Custom AI Agent with Telerik Tools 1: Configuring an LLM for Azure or Ollama</title>
    <summary type="text">Create a custom RAG-enabled AI agent for an LLM with Progress Telerik Document Processing Libraries AI connectors.</summary>
    <published>2026-04-07T20:50:49Z</published>
    <updated>2026-10-22T21:58:28Z</updated>
    <author>
      <name>Peter Vogel </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23068/17315466/creating-custom-ai-agent-telerik-tools-1-configuring-llm-azure-ollama"/>
    <content type="text"><![CDATA[<p><span class="featured">Create a custom RAG-enabled AI agent for an LLM with Progress Telerik Document Processing Libraries AI connectors.</span></p><p>The problem with general-purpose AI agents is that they are subject to &ldquo;hallucinations&rdquo; (these days, we say that those agents produce answers that aren&rsquo;t &ldquo;grounded&rdquo;).</p><p>One solution to that problem is to create a custom AI agent with your own content and have your agent use that content to generate grounded answers (this is referred to as <a target="_blank" href="https://www.telerik.com/blogs/fixing-llms-retrieval-augmented-generation">Retrieval Augmented Generation</a> or RAG). Effectively, you can create a dedicated, reliable AI assistant for your users for any aspect of your organization.</p><p>Progress Telerik <a target="_blank" href="https://www.telerik.com/document-processing-libraries">Document Processing Libraries</a> (DPL) include a set of AI connectors that support creating those custom RAG-enabled AI agents of your own using any of the existing Large Language Models (LLMs) available to you in Azure or on your own computer/server using Ollama. And, once that agent is created, Telerik UI tools will let you integrate it into any application.</p><p>So, this post kicks off a series of posts where I&rsquo;m going to walk you through <em>all</em> of the steps you need to create a custom AI agent by using Telerik DPL (and related AI tools), integrated with an LLM available on Azure or in Ollama. Once you&rsquo;ve created an agent, I&rsquo;ll show you how to add that custom agent into your application&rsquo;s UI using AI-enabled Telerik UI components in either JavaScript or Blazor (though you could also create your frontend in ASP.NET, Windows Forms or .NET MAUI).</p><p>But &hellip; having said that, if all you want to do is add an AI prompt to your application, then, thanks to Telerik tools, this post might be the only one you need. At the end of this post, I&rsquo;ll show you how to create a simple frontend for your application to use your LLM. In fact, if you&rsquo;ve already got an LLM deployed (which is what this post is about), you might want to skip the rest of this post and go directly to that last section.</p><h2 id="picking-your-llm-provider">Picking Your LLM Provider</h2><p>If you stick with Microsoft&rsquo;s offerings, you actually have three options for creating the LLM your application will use. The good news here is that, thanks to Microsoft and Telerik tools, it doesn&rsquo;t make much difference to your code which of those three options you use.</p><p>Your three choices are:</p><ul><li><p><strong>Microsoft Foundry</strong>: This gives you access to a wide variety of LLMs from multiple providers (including the OpenAI LLMs), while giving you integration with Azure security. Microsoft Foundry also gives you extensive monitoring for your LLM, enables data privacy and supports integration with other Azure services. This is Microsoft&rsquo;s recommended option. I&rsquo;ll call this the &ldquo;Foundry option.&rdquo;</p></li><li><p><strong>Azure OpenAI</strong>: This is the easiest option to set up but also provides the least access to Azure functionality. The Azure OpenAI option gives you access only to OpenAI&rsquo;s LLMs and only limited integration with other Azure resources (principally, security). With this choice you do, however, get access to the latest OpenAI LLMs, potentially before they show up in Microsoft Foundry. I&rsquo;ll call this the &ldquo;OpenAI-only option.&rdquo;</p></li><li><p><strong>Ollama</strong>: Unlike OpenAI or Microsoft Foundry, Ollama runs on your local computer, rather than in the cloud. Since there are no API fees, that makes Ollama an attractive choice for cheaper solutions (provided you have a powerful enough development computer). I&rsquo;ll call this the &ldquo;Client-side option.&rdquo;</p></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">Loading, Accessing and Converting Office and PDF Documents with Telerik Document Processing Libraries</h4></div><div class="col-8"><p class="u-fs16 u-mb0">Here&rsquo;s what you need to get started with <a target="_blank" href="https://www.telerik.com/blogs/loading-accessing-converting-office-pdf-documents-telerik-document-processing-libraries">Telerik Document Processing Libraries to work with PDF, Word and Excel files</a> (and, like any good suite, make all those document types look very much alike).</p></div></div><hr class="u-mb3" /></aside><h2 id="setting-up-openaimicrosoft-foundry">Setting Up OpenAI/Microsoft Foundry</h2><p>While I&rsquo;m treating the OpenAI-only option as a separate case from the Foundry option, the OpenAI-only option is actually hosted in Microsoft Foundry. As a result, the processes for setting up the OpenAI-only or the Foundry option are very similar:</p><ol><li>In the Azure portal at <a target="_blank" href="http://portal.azure.com">portal.azure.com</a>, create an Azure resource.</li><li>In the Microsoft Foundry portal at <a target="_blank" href="http://ai.azure.com">ai.azure.com</a>, add an LLM&mdash;a &ldquo;deployment&rdquo;&mdash;to that resource.</li></ol><h3 id="creating-the-resource">Creating the Resource</h3><p>To start, surf to the <a target="_blank" href="https://portal.azure.com">Azure Portal</a> and search for Microsoft Foundry. On the Microsoft Foundry page, in the menu on the left, expand the <strong>Use with Foundry</strong> node and click on either:</p><ul><li>The Foundry node for the Foundry option</li><li>The Azure OpenAI node for the OpenAI-only options</li></ul><p>Either choice will open a page on the right. On that new page, click on the +Create menu choice at the left end of the menu running across the top of the page. That will start the wizard that will create your Azure resource.</p><p>If you opened this page from the Foundry node, you&rsquo;ll start the wizard for creating your Foundry resource.</p><p>If you opened this page from the OpenAI node, you&rsquo;ll get a dropdown list with two choices: OpenAI and Microsoft Foundry. This is, effectively, your last chance to switch to the Foundry option instead of OpenAI-only. Pick the OpenAI choice to open the wizard for creating an OpenAI-only resource.</p><p>Regardless of which wizard you&rsquo;ve invoked, on the wizard&rsquo;s first page you&rsquo;ll need to provide a resource group, a name for your resource and a region for your LLM to run in. In addition, on the first page:</p><ul><li>For a Foundry resource, you&rsquo;ll need to provide a default project name</li><li>For an Azure OpenAI resource, you&rsquo;ll need to select a <a target="_blank" href="https://azure.microsoft.com/en-ca/pricing/details/cognitive-services/openai-service/">pricing tier</a></li></ul><p>After that, it&rsquo;s just a matter of &ldquo;Next&rdquo;ing your way through the rest of the wizard&rsquo;s tabs.</p><h3 id="setting-network-access">Setting Network Access</h3><p>Both the Foundry and OpenAI-only wizards include a Network tab to control access to your resource. You have three choices on the Network tab:</p><ul><li>All networks: Gives access to your resource from anywhere</li><li>Selected networks: Limits access to a subnet on a specific Azure virtual network, plus a list of IP addresses (e.g., your development computer)</li><li>Disabled: Allows access only through a private endpoint on an Azure virtual network</li></ul><p>You would use the last two options if you were going to run your application on a VM on a virtual network or in an App Service. You don&rsquo;t need to use the network choices to control access to your resource. You could control access to your LLM just through the resource&rsquo;s Access Control/IAM roles, for example, or your application&rsquo;s built-in security. However, for a production application, you should always lean toward more control rather than less and pick whichever of these options that makes sense for limiting network access to your resource.</p><h3 id="setting-foundry-option">Setting Foundry Option</h3><p>The Foundry wizard includes three additional tabs that the OpenAI wizard does not:</p><ul><li>Storage: This tab provides access to multiple additional Azure resources. You can:<br />&nbsp;&nbsp;&nbsp; &raquo; Tie your resource to an Azure Key Vault where you can store passwords or other credentials that agents in your resource will need<br />&nbsp;&nbsp;&nbsp; &raquo; Turn on Application Insights to log activities in your resource<br />&nbsp;&nbsp;&nbsp; &raquo; Tie in an AI Search agent that your resource can access when processing requests<br />&nbsp;&nbsp;&nbsp; &raquo; Add a Storage account to hold uploaded files<br />&nbsp;&nbsp;&nbsp; &raquo; Integrate a CosmosDB account to hold conversation history.</li><li>Identity: Assign a <a target="_blank" href="https://www.telerik.com/blogs/coding-azure-4-securing-web-service-app-service-access-azure-sql-database">Managed Identity</a> to your resource to support agents that access other Azure resources</li><li>Encryption: Use your own encryption keys for any data stored by your resource</li></ul><p>For a proof-of-concept project, you can take the defaults on all these tabs.</p><p>Both wizards finish with the standard Tags tab and Review-and-create tab. When you get to that last tab, click the Create button to create your resource.</p><h3 id="selecting-an-llm">Selecting an LLM</h3><p>Your application interacts with a deployed instance of some LLM so your next step is to create a deployment that uses one of the LLMs available to your resource.</p><p>To do that, navigate to your resource and, on the Overview page, click on the Go to Foundry portal link at the top of the page. This will open a new tab in your browser displaying the <a target="_blank" href="http://ai.azure.com">ai.azure.com</a> portal. This is where you will select the LLM you&rsquo;ll use in your deployment.</p><p>To create a deployment, on the Microsoft Foundry page for your resource, in the menu on the left, click on the Model catalog node. That will take you to a page where you can select your LLM.</p><p>A warning: Over and above whatever criteria you have for getting the right model for your application, selecting a model may require some trial-and-error poking around. That&rsquo;s because:</p><ul><li>Some models are deprecated or may not be available for your resource (e.g., selecting a non-OpenAI LLM for an OpenAI-only resource).</li><li>You may not be eligible for some models.</li><li>Your subscription may not have the necessary resources to support a model.</li><li>The model you select may not support the processing you want to implement.</li></ul><p>Once you open the page listing available LLMs, if you got to the page from a:</p><ul><li>Foundry resource: You can browse the lists of LLMs from a variety of providers or you can search for an LLM using the search box just below those lists</li><li>OpenAI resource: You can browse the list of OpenAI LLMs</li></ul><p>When you select an LLM, Azure will display a page with high-level information about your LLM. For my case study, I created three deployments, one each for these three LLMs:</p><ul><li>gpt-5.2-chat LLM as an example of a Foundry resource</li><li>gpt-5-chat LLM as an example of an Azure OpenAI-only resource</li><li>text-embedding-3-large as an example of an LLM with specific features (I&rsquo;ll use it in a later post on querying with Telerik AI connectors).</li></ul><h3 id="creating-a-deployment">Creating a Deployment</h3><p>To create your deployment, at the top of the page, click the button labeled &ldquo;Use this model.&rdquo; That will display a dialog where you can customize the LLM you&rsquo;ve chosen, including giving your deployment a name (your deployment&rsquo;s name will default to the name of your LLM). Click on the button at the bottom of the page labeled &ldquo;Create resource and deploy&rdquo; &hellip; and then be patient because this can take a moment.</p><p>After your deployment is created with your selected LLM:</p><ul><li>In a Foundry resource, you&rsquo;ll be taken to the page describing your deployment.</li><li>In an OpenAI-only resource, you&rsquo;ll be taken to an Overview page. In the menu on the left, click on the Models + endpoints node to open a list of your deployments. Select the deployment you just created to be taken to the page describing your deployment.</li></ul><p>Once you&rsquo;re on your deployment&rsquo;s page, you need three required pieces of information from the page to use in your application and one optional piece:</p><ul><li>Name: Displayed at the top of the page.</li><li>Authentication information: Your deployment will default to Key authentication, which is fine for a proof-of-concept application (you can switch to Entra Id authentication for production, but the code to access your resource will get more complicated). Assuming that you stick with Key Authentication, copy the Key provided on this page.</li><li>URL: At the top of your deployment&rsquo;s page, find the Target Uri and copy the first segment of the URL provided (i.e., everything from the &ldquo;https://&rdquo; and up to, but not including, the first forward slash in the URL).</li><li>Version: Depending on how you intend to use your LLM, you may not need this, but it does no harm to grab it now. Again, the easiest place to get this is from the sample code. Look for the string put in the variable called api-version (it will look like a date, e.g., &ldquo;2025-12-01&rdquo;).</li></ul><h3 id="returning-to-your-deployment">Returning to Your Deployment</h3><p>To return to your resource or your model, first surf to <a target="_blank" href="http://ai.azure.com">ai.azure.com</a> and sign in. This portal is, currently, in the process of migrating to a new UI, so:</p><ul><li>If you get the &ldquo;old&rdquo; UI then, you may get a list of Foundry and Open AI resources. If so, pick the resource you want to go to the resource&rsquo;s page. On the other hand, you may just be taken directly to one of your resources. Either way, in the menu on the left, select the Models + endpoints node to see a list of your deployments.</li><li>If you <em>don&rsquo;t</em> get that list of your resources &hellip; well, then you&rsquo;re probably in the new UI and, at this point, I&rsquo;d recommend using the toggle at the top of the page to return to the old UI so you can follow along.</li></ul><p>You have more to do to create your own custom RAG-enabled AI agent (load your own content, for example). But if all you want is an application that integrates an AI prompt, you&rsquo;ve done everything you need to support that. You just need to create a frontend that lets the user query your LLM&rsquo;s deployment. The Telerik AI Prompt will let you do that with minimum effort. I&rsquo;ll cover that after showing how to set up the client-side option with Ollama.</p><h2 id="setting-up-ollama">Setting Up Ollama</h2><p>To implement the client-side option, you first need to <a target="_blank" href="https://ollama.com/download/windows">download Ollama</a> and install it. To confirm that Ollama installed successfully, open a command window and enter <code>ollama</code>. You should get back a list of Ollama commands.</p><p>You next need to add the LLM you want to use to Ollama. To do that, open a command window and use Ollama&rsquo;s <code>pull</code> command to install one of the LLMs from the <a target="_blank" href="https://ollama.com/library">Ollama registry</a>. This command installs OpenAI&rsquo;s <a target="_blank" href="https://openai.com/index/introducing-gpt-oss/">gpt-oss</a> LLM:</p><pre class=" language-ollama"><code class="prism  language-ollama">ollama pull gpt-oss
</code></pre><p>After using the <code>pull</code> command, you can use Ollama&rsquo;s <code>list</code> command (which displays the installed LLMs) to confirm your installation.</p><p>The Ollama server <em>should</em> start automatically after installation (and after every reboot) and <em>should</em> be listening on port 11434 on localhost. You can confirm that by using Ollama&rsquo;s <code>serve</code> command. If server isn&rsquo;t running, it will start and report the address the server is listening on. If the server is already running, you&rsquo;ll get an error message that includes address that Ollama is listening on.</p><p>Make a note of the address (probably http://localhost:11434) and the name of the LLM you installed&mdash;you&rsquo;ll need both in your code.</p><p>I&rsquo;m building up to a creating a custom AI agent over this series of posts. But, at this point, you&rsquo;ve done everything you need to create an application with an AI prompt&mdash;it&rsquo;s just a matter of adding a frontend to your LLM.</p><h2 id="adding-a-front-end">Adding a Front End</h2><p>In my next posts, I&rsquo;ll use Telerik Document Processing Library tools to create applications that allow users to apply AI to documents. However, once you&rsquo;ve created an LLM deployment, you can tie that deployment into your application&rsquo;s UI with a few lines of code in your application&rsquo;s Program.cs file and then query your LLM through the Telerik AI Prompt component in your application&rsquo;s UI.</p><p>To do that in a .NET 8 or later application, you first need to add the Microsoft.Extensions.AI.OpenAI NuGet package to your application. After that, you need to tie your model into your application by adding the following code to your application&rsquo;s Program.cs file, inserting the information about your LLM that you gathered from your deployment&rsquo;s information page.</p><p>For either of my Foundry or OpenAI-only options that code looks like this:</p><pre class=" language-csharp"><code class="prism  language-csharp">builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token function">AddSingleton</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">AzureOpenAIClient</span><span class="token punctuation">(</span>
     <span class="token keyword">new</span> <span class="token class-name">Uri</span><span class="token punctuation">(</span>&ldquo;<span class="token operator">&lt;</span>url<span class="token operator">&gt;</span>&rdquo;<span class="token punctuation">)</span><span class="token punctuation">,</span>
     <span class="token keyword">new</span> <span class="token class-name">AzureKeyCredential</span><span class="token punctuation">(</span>&ldquo;<span class="token operator">&lt;</span>key<span class="token operator">&gt;</span>&rdquo;<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 function">AddChatClient</span><span class="token punctuation">(</span>services <span class="token operator">=</span><span class="token operator">&gt;</span> 
         services<span class="token punctuation">.</span><span class="token generic-method function">GetRequiredService<span class="token punctuation">&lt;</span>AzureOpenAIClient<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 function">GetChatClient</span><span class="token punctuation">(</span><span class="token operator">&lt;</span>deployment name<span class="token operator">&gt;</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">AsIChatClient</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>For Ollama, in addition to adding the Microsoft.Extensions.AI.OpenAI NuGet package, you&rsquo;ll also need to add the OllamaSharp and Microsoft.SemanticKernel.Connectors.Ollama packages. After that, this single line of code will integrate Ollama into your application:</p><pre class=" language-csharp"><code class="prism  language-csharp">builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token generic-method function">AddSingleton<span class="token punctuation">&lt;</span>IChatClient<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span>
    sp <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token keyword">new</span> <span class="token class-name">OllamaApiClient</span><span class="token punctuation">(</span><span class="token string">"&lt;Ollama address&gt;"</span><span class="token punctuation">,</span> <span class="token string">"&lt;model name&gt;"</span><span class="token punctuation">)</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>Your last step is to add the Telerik AIPrompt to your application&rsquo;s UI (available in <a target="_blank" href="https://www.telerik.com/design-system/docs/components/aiprompt/">JavaScript for Kendo UI</a>, <a target="_blank" href="https://www.telerik.com/blazor-ui/documentation/components/aiprompt/overview">Blazor</a>, <a target="_blank" href="https://www.telerik.com/products/aspnet-ajax/ai-prompt.aspx">ASP.NET AJAX</a>, <a target="_blank" href="https://www.telerik.com/products/winforms/aiprompt.aspx">WinForms</a> and <a target="_blank" href="https://www.telerik.com/maui-ui/aiprompt">.NET MAUI</a>). That can be as simple as this in an application that supports Razor (e.g., ASP.NET or Blazor):</p><pre class=" language-razor"><code class="prism  language-razor">&lt;TelerikAIPrompt/&gt;
</code></pre><p>And that would give you this UI:</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-03/ai-ui-1-combined.png?sfvrsn=5736fd30_2" alt="The basic AI Prompt display showing a textbox with a “Generate” button underneath it. Above the textbox are two additional buttons: “Ask AI” and “Output”" /></p><p>In a JavaScript application, you&rsquo;ll need to add a <code>&lt;div&gt;</code> element, set the element&rsquo;s <code>id</code> attribute, and write a couple lines of JavaScript code. I&rsquo;ll cover that in a later post in this series.</p><p>You can now start your application, enter a prompt into the AIPrompt component (e.g., &ldquo;Tell me about RAG-enabled AI applications&rdquo;) and get a response.</p><p>There&rsquo;s obviously far more that you can do with the AI Prompt &hellip; and I&rsquo;ll return to that after my next post. Before that, however, I&rsquo;m going to roll in Telerik Document Processing Library to let you load the content for your custom AI agent. <a href="https://www.telerik.com/blogs/creating-custom-ai-agent-telerik-tools-2-loading-accessing-agent-content" target="_blank"><strong>Read Part 2!</strong></a></p><hr /><p>Explore Telerik Document Processing Libraries, plus component libraries, reporting and more with a free trial of the Telerik DevCraft bundle:</p><p><a href="https://www.telerik.com/download" class="Btn" target="_blank">Try DevCraft</a></p><img src="https://feeds.telerik.com/link/23068/17315466.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:88e4a370-d2a2-4d78-ae1f-86792f7c61e1</id>
    <title type="text">Loading, Accessing and Converting Office and PDF Documents with Telerik Document Processing Libraries</title>
    <summary type="text">Here’s what you need to get started with Telerik Document Processing Libraries to work with PDF, Word and Excel files (and, like any good suite, make all those document types look very much alike).</summary>
    <published>2026-03-31T15:30:52Z</published>
    <updated>2026-10-22T21:58:28Z</updated>
    <author>
      <name>Peter Vogel </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/23068/17310581/loading-accessing-converting-office-pdf-documents-telerik-document-processing-libraries"/>
    <content type="text"><![CDATA[<p><span class="featured">Here&rsquo;s what you need to get started with Telerik Document Processing Libraries to work with PDF, Word and Excel files (and, like any good suite, make all those document types look very much alike).</span></p><p>Progress Telerik Document Processing Libraries, in addition to letting you work with a variety of document formats (PDF, DOCX, RTF, HTML, XLSX and more), are an example of the reason you buy into a suite of tools: All the tools bear a &ldquo;family resemblance.&rdquo;</p><p>The ideal scenario, of course, would be for a single tool that made all these document formats look the same. Given the differences in format and functionality between, for example, a PDF, a Microsoft Word (or RTF or HTML) document and an Excel spreadsheet, that&rsquo;s not reasonable (though Progress Telerik has achieved that with DOCX, RTF and HTML documents).</p><p>The good news here is that, with Telerik Document Processing Libraries (DPL), the family resemblance is strong enough that, for <em>all</em> of the document types the library supports, I can show you how to load documents, start the editing process, convert between various document types and save a document in this one post.</p><h2 id="configuring-your-project">Configuring Your Project</h2><p>The sample code in this post was all written using the DPL for Windows libraries (even though I was working in ASP.NET Core&mdash;the more technical name for the version I used is &ldquo;.NET(Target OS: Windows).&rdquo; The suite is <a target="_blank" href="https://docs.telerik.com/devtools/document-processing/introduction#required-references">also available for the .NET Framework</a>.</p><p>To create an ASP.NET Core project that would work with &ldquo;all the documents,&rdquo; I added these NuGet packages to my project:</p><ul><li>To work with PDF files: Telerik.Windows.Documents.Fixed</li><li>To work with DOCX, HTML and RTF files: Telerik.Windows.Documents.Flow</li><li>To work with Excel spreadsheets: Telerik.Windows.Documents.Spreadsheet</li></ul><p>For the Excel spreadsheets, I&rsquo;m only going to work with XLSX files, so I added the Telerik.Windows.Documents.Spreadsheet.FormatProviders.OpenXml package to my project. (If I was going to work with, for example, XLS spreadsheet, then I would have added the Telerik.Documents.Spreadsheet.FormatProviders.Xls package.)</p><h2 id="loading-your-document">Loading Your Document</h2><p>The code to load a document from a file into any of these libraries is very similar:</p><ol><li>Create the appropriate provider.</li><li>Use the .NET<code>File</code> object&rsquo;s <code>OpenRead</code> to create a <code>Stream</code> that points to the file.</li><li>Use the provider&rsquo;s <code>Import</code> method to load the stream into the document object.</li></ol><p>Here, for example, is the code to load a PDF file into a <code>RadFixedDocument</code> object (I used this code in an ASP.NET Core application with documents in my project&rsquo;s wwwroot folder):</p><pre class=" language-csharp"><code class="prism  language-csharp">RadFixedDocument doc<span class="token punctuation">;</span>
PdfFormatProvider prov <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">using</span> <span class="token punctuation">(</span>Stream str <span class="token operator">=</span> File<span class="token punctuation">.</span><span class="token function">OpenRead</span><span class="token punctuation">(</span><span class="token string">@"wwwroot/documents/Priorities.pdf"</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    doc <span class="token operator">=</span> prov<span class="token punctuation">.</span><span class="token function">Import</span><span class="token punctuation">(</span>str<span class="token punctuation">,</span> TimeSpan<span class="token punctuation">.</span><span class="token function">FromSeconds</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>And here&rsquo;s the code to load a DOCX file into a <code>RadFlowDocument</code>:</p><pre class=" language-c"><code class="prism # language-c">RadFlowDocument doc<span class="token punctuation">;</span>
DocxFormatProvider prov <span class="token operator">=</span> <span class="token function">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token function">using</span> <span class="token punctuation">(</span>Stream str <span class="token operator">=</span> File<span class="token punctuation">.</span><span class="token function">OpenRead</span><span class="token punctuation">(</span>@<span class="token string">"wwwroot/documents/Priorities.docx"</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    doc <span class="token operator">=</span> prov<span class="token punctuation">.</span><span class="token function">Import</span><span class="token punctuation">(</span>str<span class="token punctuation">,</span> TimeSpan<span class="token punctuation">.</span><span class="token function">FromSeconds</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>As you can see, the code is identical except for the provider (<code>PdfFormatProvider</code> vs. <code>DocxFormatProvider</code>) and document objects (<code>RadFlowDocument</code> vs. <code>RadFixedDocument</code>).</p><p>Because both RTF and HTML documents load into the same <code>RadFlowDocument</code> object as a DOCX document, only the provider object changes (<code>HtmlFormatProvider</code> or <code>RtfFormatProdiver</code> instead of <code>DocxFormatProvider</code>) when working with those file formats. Here&rsquo;s the code to load an RTF document:</p><pre class=" language-csharp"><code class="prism  language-csharp">RadFlowDocument doc<span class="token punctuation">;</span>
RtfFormatProvider prov <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">using</span> <span class="token punctuation">(</span>Stream str <span class="token operator">=</span> File<span class="token punctuation">.</span><span class="token function">OpenRead</span><span class="token punctuation">(</span><span class="token string">@"wwwroot/documents/Priorities.rtf"</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    doc <span class="token operator">=</span> prov<span class="token punctuation">.</span><span class="token function">Import</span><span class="token punctuation">(</span>str<span class="token punctuation">,</span> TimeSpan<span class="token punctuation">.</span><span class="token function">FromSeconds</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>And here&rsquo;s the almost identical code to load an HTML file:</p><pre class=" language-csharp"><code class="prism  language-csharp">RadFlowDocument doc<span class="token punctuation">;</span>
HtmlFormatProvider prov <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">using</span> <span class="token punctuation">(</span>Stream str <span class="token operator">=</span> File<span class="token punctuation">.</span><span class="token function">OpenRead</span><span class="token punctuation">(</span><span class="token string">@"wwwroot/documents/Priorities.html"</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    doc <span class="token operator">=</span> prov<span class="token punctuation">.</span><span class="token function">Import</span><span class="token punctuation">(</span>str<span class="token punctuation">,</span> TimeSpan<span class="token punctuation">.</span><span class="token function">FromSeconds</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>The code to load an Excel workbook is also similar to what you&rsquo;ve seen before, just swapping in a new document object (<code>Workbook</code>) and provider (<code>XlsxFormatProvider</code>):</p><pre class=" language-csharp"><code class="prism  language-csharp">Workbook doc<span class="token punctuation">;</span>
XlsxFormatProvider prov <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">using</span> <span class="token punctuation">(</span>Stream str <span class="token operator">=</span> File<span class="token punctuation">.</span><span class="token function">OpenRead</span><span class="token punctuation">(</span><span class="token string">@"wwwroot/documents/priority.xlsx"</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    doc <span class="token operator">=</span> prov<span class="token punctuation">.</span><span class="token function">Import</span><span class="token punctuation">(</span>str<span class="token punctuation">,</span> TimeSpan<span class="token punctuation">.</span><span class="token function">FromSeconds</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>A note: The Workbook object assumes that you&rsquo;re going to load the <em>whole</em> Excel workbook into memory. For very large workbooks, that may not make sense. For that scenario, you should look at the <a target="_blank" href="https://docs.telerik.com/devtools/document-processing/libraries/radspreadstreamprocessing/overview">SpreadStreamProcessing Library</a>.</p><h2 id="modifying-the-documents">Modifying the Documents</h2><p>Once you&rsquo;ve loaded the documents, you can start working with them. You can often simplify your code by using the <code>RadFixedDocumentEditor</code> with PDF documents or the <code>RadFlowDocumentEditor</code>with Word/RTF/HTML documents. Not surprisingly, the code for creating an editor is almost identical for these two document types: create an editor object and pass the document you want loaded into the editor.</p><p>The code to create an editor for a PDF document looks like this:</p><pre class=" language-csharp"><code class="prism  language-csharp">RadFixedDocumentEditor editor <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">RadFixedDocumentEditor</span><span class="token punctuation">(</span>doc<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>The code for Word/RTF/HTML documents looks like this:</p><pre class=" language-csharp"><code class="prism  language-csharp">RadFlowDocumentEditor editor <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">RadFlowDocumentEditor</span><span class="token punctuation">(</span>doc<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>That&rsquo;s not to say that, as you start working with those documents, there aren&rsquo;t going to be differences. These are, after all, very different kinds of documents. Having said that, some functionality does work in a similar way across all the document types.</p><p>If, for example, I want to search a PDF document for the text &ldquo;ASP.NET,&rdquo; I create a <code>TextSearch</code> instance from my document object. I then use the <code>TextSearch</code> object&rsquo;s <code>FindAll</code> method to search for text in my PDF document, passing two things: my search text and a <code>TextSearchOptions</code> object that specifies how I want my search conducted. That <code>FindAll</code> object returns a collection of <code>SearchResult</code> objects that I can loop through.</p><p>Typical code, then, looks like this:</p><pre class=" language-csharp"><code class="prism  language-csharp">TextSearch search <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">TextSearch</span><span class="token punctuation">(</span>doc<span class="token punctuation">)</span><span class="token punctuation">;</span>
TextSearchOptions opts <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
CaseSensitive <span class="token operator">=</span> <span class="token keyword">false</span><span class="token punctuation">,</span>
WholeWordsOnly <span class="token operator">=</span> <span class="token keyword">true</span><span class="token punctuation">,</span>
UseRegularExpression <span class="token operator">=</span> <span class="token keyword">true</span>
                                                              <span class="token punctuation">}</span><span class="token punctuation">;</span>

IEnumerable<span class="token operator">&lt;</span>SearchResult<span class="token operator">&gt;</span> items <span class="token operator">=</span> search<span class="token punctuation">.</span><span class="token function">FindAll</span><span class="token punctuation">(</span><span class="token string">"ASP.NET"</span><span class="token punctuation">,</span> opts<span class="token punctuation">)</span><span class="token punctuation">;</span>

Debug<span class="token punctuation">.</span><span class="token function">Print</span><span class="token punctuation">(</span>$<span class="token string">"Found {items.Count()} items."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">foreach</span> <span class="token punctuation">(</span>SearchResult item <span class="token keyword">in</span> items<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    Debug<span class="token punctuation">.</span><span class="token function">Print</span><span class="token punctuation">(</span>$<span class="token string">"Found at {item.Range.StartPosition} "</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    Debug<span class="token punctuation">.</span><span class="token function">Print</span><span class="token punctuation">(</span>$<span class="token string">"Found: {item.Result}"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>The process is almost identical for the <code>RadFlowDocument</code> object, except:</p><ul><li>You call the <code>FindAll</code>method directly from the <code>RadFlowDocumentEditor</code>.</li><li>There isn&rsquo;t a separate options object (though all the search options from the <code>TextSearch</code> object are still available).</li><li>The <code>FindAll</code> method on the editor returns <code>FindResult</code> objects instead of <code>SearchResult</code> objects.</li></ul><p>As a result, the equivalent search code for a DOCX, RTF or HTML document looks like this:</p><pre class=" language-csharp"><code class="prism  language-csharp">RadFlowDocumentEditor editor <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">RadFlowDocumentEditor</span><span class="token punctuation">(</span>doc<span class="token punctuation">)</span><span class="token punctuation">;</span>

IEnumerable<span class="token operator">&lt;</span>FindResult<span class="token operator">&gt;</span> items <span class="token operator">=</span> editor<span class="token punctuation">.</span><span class="token function">FindAll</span><span class="token punctuation">(</span><span class="token string">"ASP.NET"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

Debug<span class="token punctuation">.</span><span class="token function">Print</span><span class="token punctuation">(</span>$<span class="token string">"Found {items.Count()} items."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">foreach</span> <span class="token punctuation">(</span>FindResult item <span class="token keyword">in</span> items<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    Debug<span class="token punctuation">.</span><span class="token function">Print</span><span class="token punctuation">(</span>$<span class="token string">"Found at {item.RelativeStartIndex} "</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    Debug<span class="token punctuation">.</span><span class="token function">Print</span><span class="token punctuation">(</span>$<span class="token string">"Found: {item.FullMatchText}"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>Searching a spreadsheet works similarly. The differences:</p><ul><li>The <code>FindAll</code> method is built right into the <code>Workbook</code> object.</li><li>You have more search options available with the spreadsheet object.</li><li>You pass your search string as part of the options object.</li></ul><p>My find code with a workbook would look like this:</p><pre class=" language-csharp"><code class="prism  language-csharp">FindOptions opts <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
FindWhat <span class="token operator">=</span> <span class="token string">"ASP.NET"</span><span class="token punctuation">,</span>
MatchCase <span class="token operator">=</span> <span class="token keyword">false</span><span class="token punctuation">,</span>
MatchEntireCellContents <span class="token operator">=</span> <span class="token keyword">true</span><span class="token punctuation">,</span>
                                                <span class="token punctuation">}</span><span class="token punctuation">;</span>

IEnumerable<span class="token operator">&lt;</span>FindResult<span class="token operator">&gt;</span> items <span class="token operator">=</span> doc<span class="token punctuation">.</span><span class="token function">FindAll</span><span class="token punctuation">(</span>opts<span class="token punctuation">)</span><span class="token punctuation">;</span>

Debug<span class="token punctuation">.</span><span class="token function">Print</span><span class="token punctuation">(</span>$<span class="token string">"Found {items.Count()} items."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">foreach</span><span class="token punctuation">(</span>FindResult item <span class="token keyword">in</span> items<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    Debug<span class="token punctuation">.</span><span class="token function">Print</span><span class="token punctuation">(</span>$<span class="token string">"Found at {item.FoundCell.CellIndex} "</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    Debug<span class="token punctuation">.</span><span class="token function">Print</span><span class="token punctuation">(</span>$<span class="token string">"Found: {item.ResultValue}"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>One note: It is certainly convenient when objects from the different libraries share the same name (like the <code>FindResult</code> object that&rsquo;s defined in both the <code>RadFlowDocument</code> and <code>Workbook</code> libraries). However, if you try to use both <code>FindResult</code> objects in the same code file, the compiler will get confused because the two objects are in different namespaces. In the unlikely case that you&rsquo;re working with both Excel and Word documents in the same code file, you&rsquo;ll have to fully qualify the object names&mdash;something I haven&rsquo;t done in this post.</p><h2 id="saving-your-documents">Saving Your Documents</h2><p>To save your modified documents back to disk, you just need to use the provider&rsquo;s <code>Export</code> method. The code for all the document types is identical:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">using</span> <span class="token punctuation">(</span>Stream str <span class="token operator">=</span> File<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span><span class="token string">@"wwwroot/documents/Prioritiesnew.&lt;filetype&gt;"</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    prov<span class="token punctuation">.</span><span class="token function">Export</span><span class="token punctuation">(</span>doc<span class="token punctuation">,</span>str<span class="token punctuation">,</span> TimeSpan<span class="token punctuation">.</span><span class="token function">FromSeconds</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><h2 id="converting-documents">Converting Documents</h2><p>You can convert from one type in the suite to other types and, not surprisingly, the conversion processes look very much alike. As you&rsquo;ve seen before, it often comes down to using the right provider.</p><p>If, for example, you want plain text version of your PDF file, you use the <code>TextFormatProvider</code> object&rsquo;s <code>Export</code> method:</p><pre class=" language-csharp"><code class="prism  language-csharp">TextFormatProvider prov <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">string</span> text <span class="token operator">=</span> prov<span class="token punctuation">.</span><span class="token function">Export</span><span class="token punctuation">(</span>doc<span class="token punctuation">,</span> TimeSpan<span class="token punctuation">.</span><span class="token function">FromSeconds</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>For Word/HTML/RTF document types, the code is almost identical except it uses the <code>TxtFormatProvider</code> object:</p><pre class=" language-csharp"><code class="prism  language-csharp">TxtFormatProvider txProv <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">string</span> text <span class="token operator">=</span> prov<span class="token punctuation">.</span><span class="token function">Export</span><span class="token punctuation">(</span>doc<span class="token punctuation">,</span> TimeSpan<span class="token punctuation">.</span><span class="token function">FromSeconds</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            
</code></pre><p>One note: There is a downside to having the classes that do similar things to different document types have the same name. If you are mixing document types and, as a result, using the Fixed, Flow and spreadsheet libraries in the same application, the compiler can get confused about which class from which library you&rsquo;re using. If so, you&rsquo;ll have to <a target="_blank" href="https://docs.telerik.com/devtools/document-processing/knowledge-base/cs0104-error-pdf-format-provider">fully qualify your class names</a> by including their namespaces in the class names. That makes for hard-to-read code, so I haven&rsquo;t done that here.</p><p>But, as an example of how different documents require different functionality, you probably wouldn&rsquo;t ever want to convert an Excel workbook to a string &hellip; but you might want to save your workbook as a CSV file. As you might expect by now, the code to save your imported workbook into a CSV file just means using the <code>Export</code> method on the appropriate provider&mdash;the <code>CsvFormatProvider</code> object in this case.</p><p>Typical code would look like this:</p><pre class=" language-csharp"><code class="prism  language-csharp">CsvFormatProvider prov <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">using</span> <span class="token punctuation">(</span>Stream str <span class="token operator">=</span> File<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span><span class="token string">"Priority.csv"</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    prov<span class="token punctuation">.</span><span class="token function">Export</span><span class="token punctuation">(</span>doc<span class="token punctuation">,</span> str<span class="token punctuation">,</span> TimeSpan<span class="token punctuation">.</span><span class="token function">FromSeconds</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p><a target="_blank" href="https://www.telerik.com/blogs/quick-and-easy-conversion-to-pdf-with-telerik-document-processing-library">Converting any of these document types (Excel, Word, HTML, etc.) to PDF</a> is equally straightforward. Because all the libraries look very much alike, it&rsquo;s really just a matter of adding the library with the provider you need.</p><p>But you can also convert your <code>Workbook</code> object into a <code>RadFixedDocument</code> if you wanted to manipulate your spreadsheet as a PDF object. That conversion is handled by the PdfFormatProvider from the Telerik.Windows.Documents.Spreadsheet.Formatproviders.Pdf package and using its <code>ExportToFixedDocument</code>method:</p><pre class=" language-csharp"><code class="prism  language-csharp">PdfFormatProvider prov <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

RadFixedDocument fixedDoc <span class="token operator">=</span> 
        prov<span class="token punctuation">.</span><span class="token function">ExportToFixedDocument</span><span class="token punctuation">(</span>doc<span class="token punctuation">,</span> TimeSpan<span class="token punctuation">.</span><span class="token function">FromSeconds</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>The code is identical if you want to convert a Word/RTF/HTML document to a <code>RadFixedDocument</code> to work with it as a PDF file. That conversion also uses a <code>PdfFormatProvider</code> object but, this time, from the Telerik.Windows.Documents.Flow.FormatProviders.Pdf namespace.</p><p>But, because the providers from the two libraries have the same name, if you&rsquo;re using both libraries in the same code file, you will need to fully qualify your provider names to make sure you&rsquo;re getting the appropriate <code>PdfFormatProvider</code>.</p><p>Of course, once you start using these tools to create or modify the documents you&rsquo;ve loaded, you&rsquo;ll find more differences&mdash;the functionality in a spreadsheet is very different from the functionality in an HTML document. But, while the family resemblances among this suite won&rsquo;t eliminate those differences, it does cut those differences down to what matters: how those documents differ in their functionality. Which is, after all, what you want.</p><hr /><p>Explore Telerik Document Processing Libraries, plus component libraries, reporting and more with a free trial of the Telerik DevCraft bundle:</p><p><a href="https://www.telerik.com/download" class="Btn" target="_blank">Try DevCraft</a></p><img src="https://feeds.telerik.com/link/23068/17310581.gif" height="1" width="1"/>]]></content>
  </entry>
</feed>
